mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-21 12:58:07 -05:00
Compare commits
39 Commits
fix/deploy
...
v0.5.66
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45371e521e | ||
|
|
5988d0e46f | ||
|
|
145db9d8c3 | ||
|
|
0ce0f98aa5 | ||
|
|
294b168ed9 | ||
|
|
0dc2c1fe0d | ||
|
|
fb90c4e9b1 | ||
|
|
0af96d06c6 | ||
|
|
dff1c9d083 | ||
|
|
b09f683072 | ||
|
|
a8bb0db660 | ||
|
|
af82820a28 | ||
|
|
4372841797 | ||
|
|
5e8c843241 | ||
|
|
7bf3d73ee6 | ||
|
|
7ffc11a738 | ||
|
|
be578e2ed7 | ||
|
|
f415e5edc4 | ||
|
|
13a6e6c3fa | ||
|
|
f5ab7f21ae | ||
|
|
bfb6fffe38 | ||
|
|
4fbec0a43f | ||
|
|
585f5e365b | ||
|
|
3792bdd252 | ||
|
|
eb5d1f3e5b | ||
|
|
54ab82c8dd | ||
|
|
f895bf469b | ||
|
|
dd3209af06 | ||
|
|
b6ba3b50a7 | ||
|
|
b304233062 | ||
|
|
57e4b49bd6 | ||
|
|
e12dd204ed | ||
|
|
3d9d9cbc54 | ||
|
|
0f4ec962ad | ||
|
|
4827866f9a | ||
|
|
3e697d9ed9 | ||
|
|
4431a1a484 | ||
|
|
4d1a9a3f22 | ||
|
|
eb07a080fb |
@@ -11,7 +11,7 @@ import { preprocessExecution } from '@/lib/execution/preprocessing'
|
||||
import { LoggingSession } from '@/lib/logs/execution/logging-session'
|
||||
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
|
||||
import { createStreamingResponse } from '@/lib/workflows/streaming/streaming'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import { setFormAuthCookie, validateFormAuth } from '@/app/api/form/utils'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
@@ -36,7 +36,7 @@ async function getWorkflowInputSchema(workflowId: string): Promise<any[]> {
|
||||
.from(workflowBlocks)
|
||||
.where(eq(workflowBlocks.workflowId, workflowId))
|
||||
|
||||
const startBlock = blocks.find((block) => isValidStartBlockType(block.type))
|
||||
const startBlock = blocks.find((block) => isInputDefinitionTrigger(block.type))
|
||||
|
||||
if (!startBlock) {
|
||||
return []
|
||||
|
||||
@@ -234,7 +234,7 @@ function ProgressBar({
|
||||
{segments.map((segment, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='absolute h-full'
|
||||
className='absolute h-full opacity-70'
|
||||
style={{
|
||||
left: `${segment.startPercent}%`,
|
||||
width: `${segment.widthPercent}%`,
|
||||
|
||||
@@ -257,7 +257,7 @@ export const LogDetails = memo(function LogDetails({
|
||||
Version
|
||||
</span>
|
||||
<div className='flex w-0 flex-1 justify-end'>
|
||||
<span className='max-w-full truncate rounded-[6px] bg-[#14291B] px-[9px] py-[2px] font-medium text-[#86EFAC] text-[12px]'>
|
||||
<span className='max-w-full truncate rounded-[6px] bg-[#bbf7d0] px-[9px] py-[2px] font-medium text-[#15803d] text-[12px] dark:bg-[#14291B] dark:text-[#86EFAC]'>
|
||||
{log.deploymentVersionName || `v${log.deploymentVersion}`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { DatePicker } from '@/components/emcn/components/date-picker/date-picker
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { hasActiveFilters } from '@/lib/logs/filters'
|
||||
import { getTriggerOptions } from '@/lib/logs/get-trigger-options'
|
||||
import { type LogStatus, STATUS_CONFIG } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
import { useFilterStore } from '@/stores/logs/filters/store'
|
||||
@@ -211,12 +212,12 @@ export function LogsToolbar({
|
||||
}, [level])
|
||||
|
||||
const statusOptions: ComboboxOption[] = useMemo(
|
||||
() => [
|
||||
{ value: 'error', label: 'Error', icon: getColorIcon('var(--text-error)') },
|
||||
{ value: 'info', label: 'Info', icon: getColorIcon('var(--terminal-status-info-color)') },
|
||||
{ value: 'running', label: 'Running', icon: getColorIcon('#22c55e') },
|
||||
{ value: 'pending', label: 'Pending', icon: getColorIcon('#f59e0b') },
|
||||
],
|
||||
() =>
|
||||
(Object.keys(STATUS_CONFIG) as LogStatus[]).map((status) => ({
|
||||
value: status,
|
||||
label: STATUS_CONFIG[status].label,
|
||||
icon: getColorIcon(STATUS_CONFIG[status].color),
|
||||
})),
|
||||
[]
|
||||
)
|
||||
|
||||
@@ -242,12 +243,8 @@ export function LogsToolbar({
|
||||
|
||||
const selectedStatusColor = useMemo(() => {
|
||||
if (selectedStatuses.length !== 1) return null
|
||||
const status = selectedStatuses[0]
|
||||
if (status === 'error') return 'var(--text-error)'
|
||||
if (status === 'info') return 'var(--terminal-status-info-color)'
|
||||
if (status === 'running') return '#22c55e'
|
||||
if (status === 'pending') return '#f59e0b'
|
||||
return null
|
||||
const status = selectedStatuses[0] as LogStatus
|
||||
return STATUS_CONFIG[status]?.color ?? null
|
||||
}, [selectedStatuses])
|
||||
|
||||
const workflowOptions: ComboboxOption[] = useMemo(
|
||||
|
||||
@@ -5,7 +5,6 @@ import { getIntegrationMetadata } from '@/lib/logs/get-trigger-options'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types'
|
||||
|
||||
/** Column configuration for logs table - shared between header and rows */
|
||||
export const LOG_COLUMNS = {
|
||||
date: { width: 'w-[8%]', minWidth: 'min-w-[70px]', label: 'Date' },
|
||||
time: { width: 'w-[12%]', minWidth: 'min-w-[90px]', label: 'Time' },
|
||||
@@ -16,10 +15,8 @@ export const LOG_COLUMNS = {
|
||||
duration: { width: 'w-[20%]', minWidth: 'min-w-[100px]', label: 'Duration' },
|
||||
} as const
|
||||
|
||||
/** Type-safe column key derived from LOG_COLUMNS */
|
||||
export type LogColumnKey = keyof typeof LOG_COLUMNS
|
||||
|
||||
/** Ordered list of column keys for rendering table headers */
|
||||
export const LOG_COLUMN_ORDER: readonly LogColumnKey[] = [
|
||||
'date',
|
||||
'time',
|
||||
@@ -30,7 +27,6 @@ export const LOG_COLUMN_ORDER: readonly LogColumnKey[] = [
|
||||
'duration',
|
||||
] as const
|
||||
|
||||
/** Possible execution status values for workflow logs */
|
||||
export type LogStatus = 'error' | 'pending' | 'running' | 'info' | 'cancelled'
|
||||
|
||||
/**
|
||||
@@ -53,30 +49,28 @@ export function getDisplayStatus(status: string | null | undefined): LogStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/** Configuration mapping log status to Badge variant and display label */
|
||||
const STATUS_VARIANT_MAP: Record<
|
||||
export const STATUS_CONFIG: Record<
|
||||
LogStatus,
|
||||
{ variant: React.ComponentProps<typeof Badge>['variant']; label: string }
|
||||
{ variant: React.ComponentProps<typeof Badge>['variant']; label: string; color: string }
|
||||
> = {
|
||||
error: { variant: 'red', label: 'Error' },
|
||||
pending: { variant: 'amber', label: 'Pending' },
|
||||
running: { variant: 'green', label: 'Running' },
|
||||
cancelled: { variant: 'gray', label: 'Cancelled' },
|
||||
info: { variant: 'gray', label: 'Info' },
|
||||
error: { variant: 'red', label: 'Error', color: 'var(--text-error)' },
|
||||
pending: { variant: 'amber', label: 'Pending', color: '#f59e0b' },
|
||||
running: { variant: 'green', label: 'Running', color: '#22c55e' },
|
||||
cancelled: { variant: 'orange', label: 'Cancelled', color: '#f97316' },
|
||||
info: { variant: 'gray', label: 'Info', color: 'var(--terminal-status-info-color)' },
|
||||
}
|
||||
|
||||
/** Configuration mapping core trigger types to Badge color variants */
|
||||
const TRIGGER_VARIANT_MAP: Record<string, React.ComponentProps<typeof Badge>['variant']> = {
|
||||
manual: 'gray-secondary',
|
||||
api: 'blue',
|
||||
schedule: 'green',
|
||||
chat: 'purple',
|
||||
webhook: 'orange',
|
||||
mcp: 'cyan',
|
||||
a2a: 'teal',
|
||||
}
|
||||
|
||||
interface StatusBadgeProps {
|
||||
/** The execution status to display */
|
||||
status: LogStatus
|
||||
}
|
||||
|
||||
@@ -86,14 +80,13 @@ interface StatusBadgeProps {
|
||||
* @returns A Badge with dot indicator and status label
|
||||
*/
|
||||
export const StatusBadge = React.memo(({ status }: StatusBadgeProps) => {
|
||||
const config = STATUS_VARIANT_MAP[status]
|
||||
const config = STATUS_CONFIG[status]
|
||||
return React.createElement(Badge, { variant: config.variant, dot: true }, config.label)
|
||||
})
|
||||
|
||||
StatusBadge.displayName = 'StatusBadge'
|
||||
|
||||
interface TriggerBadgeProps {
|
||||
/** The trigger type identifier (e.g., 'manual', 'api', or integration block type) */
|
||||
trigger: string
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { memo, useCallback } from 'react'
|
||||
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, LogOut } from 'lucide-react'
|
||||
import { Button, Copy, Tooltip, Trash2 } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
@@ -90,7 +90,7 @@ export const ActionBar = memo(
|
||||
|
||||
const userPermissions = useUserPermissionsContext()
|
||||
|
||||
const isStartBlock = isValidStartBlockType(blockType)
|
||||
const isStartBlock = isInputDefinitionTrigger(blockType)
|
||||
const isResponseBlock = blockType === 'response'
|
||||
const isNoteBlock = blockType === 'note'
|
||||
const isSubflowBlock = blockType === 'loop' || blockType === 'parallel'
|
||||
@@ -142,7 +142,7 @@ export const ActionBar = memo(
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
{!isStartBlock && !isResponseBlock && !isSubflowBlock && (
|
||||
{!isStartBlock && !isResponseBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
@@ -213,6 +213,29 @@ export const ActionBar = memo(
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
{isSubflowBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (!disabled) {
|
||||
collaborativeBatchToggleBlockEnabled([blockId])
|
||||
}
|
||||
}}
|
||||
className={ACTION_BUTTON_STYLES}
|
||||
disabled={disabled}
|
||||
>
|
||||
{isEnabled ? <Circle className={ICON_SIZE} /> : <CircleOff className={ICON_SIZE} />}
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
{getTooltipMessage(isEnabled ? 'Disable Block' : 'Enable Block')}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
PopoverDivider,
|
||||
PopoverItem,
|
||||
} from '@/components/emcn'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
|
||||
|
||||
/**
|
||||
* Block information for context menu actions
|
||||
@@ -74,12 +74,16 @@ export function BlockMenu({
|
||||
const allEnabled = selectedBlocks.every((b) => b.enabled)
|
||||
const allDisabled = selectedBlocks.every((b) => !b.enabled)
|
||||
|
||||
const hasStarterBlock = selectedBlocks.some((b) => isValidStartBlockType(b.type))
|
||||
const hasSingletonBlock = selectedBlocks.some(
|
||||
(b) =>
|
||||
TriggerUtils.requiresSingleInstance(b.type) || TriggerUtils.isSingleInstanceBlockType(b.type)
|
||||
)
|
||||
const hasTriggerBlock = selectedBlocks.some((b) => TriggerUtils.isTriggerBlock(b))
|
||||
const allNoteBlocks = selectedBlocks.every((b) => b.type === 'note')
|
||||
const isSubflow =
|
||||
isSingleBlock && (selectedBlocks[0]?.type === 'loop' || selectedBlocks[0]?.type === 'parallel')
|
||||
|
||||
const canRemoveFromSubflow = showRemoveFromSubflow && !hasStarterBlock
|
||||
const canRemoveFromSubflow = showRemoveFromSubflow && !hasTriggerBlock
|
||||
|
||||
const getToggleEnabledLabel = () => {
|
||||
if (allEnabled) return 'Disable'
|
||||
@@ -127,7 +131,7 @@ export function BlockMenu({
|
||||
<span>Paste</span>
|
||||
<span className='ml-auto opacity-70 group-hover:opacity-100'>⌘V</span>
|
||||
</PopoverItem>
|
||||
{!hasStarterBlock && (
|
||||
{!hasSingletonBlock && (
|
||||
<PopoverItem
|
||||
disabled={disableEdit}
|
||||
onClick={() => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Skeleton } from '@/components/ui'
|
||||
import { isDev } from '@/lib/core/config/feature-flags'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { getBaseUrl, getEmailDomain } from '@/lib/core/utils/urls'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import {
|
||||
type FieldConfig,
|
||||
useCreateForm,
|
||||
@@ -147,7 +147,7 @@ export function FormDeploy({
|
||||
|
||||
useEffect(() => {
|
||||
const blocks = Object.values(useWorkflowStore.getState().blocks)
|
||||
const startBlock = blocks.find((b) => isValidStartBlockType(b.type))
|
||||
const startBlock = blocks.find((b) => isInputDefinitionTrigger(b.type))
|
||||
|
||||
if (startBlock) {
|
||||
const inputFormat = useSubBlockStore.getState().getValue(startBlock.id, 'inputFormat')
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
Textarea,
|
||||
} from '@/components/emcn'
|
||||
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import type { InputFormatField } from '@/lib/workflows/types'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
@@ -52,7 +52,7 @@ export function ApiInfoModal({ open, onOpenChange, workflowId }: ApiInfoModalPro
|
||||
for (const [blockId, block] of Object.entries(blocks)) {
|
||||
if (!block || typeof block !== 'object') continue
|
||||
const blockType = (block as { type?: string }).type
|
||||
if (blockType && isValidStartBlockType(blockType)) {
|
||||
if (blockType && isInputDefinitionTrigger(blockType)) {
|
||||
return blockId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { Skeleton } from '@/components/ui'
|
||||
import { generateToolInputSchema, sanitizeToolName } from '@/lib/mcp/workflow-tool-schema'
|
||||
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import type { InputFormatField } from '@/lib/workflows/types'
|
||||
import {
|
||||
useAddWorkflowMcpTool,
|
||||
@@ -107,7 +107,7 @@ export function McpDeploy({
|
||||
for (const [blockId, block] of Object.entries(blocks)) {
|
||||
if (!block || typeof block !== 'object') continue
|
||||
const blockType = (block as { type?: string }).type
|
||||
if (blockType && isValidStartBlockType(blockType)) {
|
||||
if (blockType && isInputDefinitionTrigger(blockType)) {
|
||||
return blockId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { memo, useMemo, useRef } from 'react'
|
||||
import { RepeatIcon, SplitIcon } from 'lucide-react'
|
||||
import { Handle, type NodeProps, Position, useReactFlow } from 'reactflow'
|
||||
import { Badge } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { HANDLE_POSITIONS } from '@/lib/workflows/blocks/block-dimensions'
|
||||
import { type DiffStatus, hasDiffStatus } from '@/lib/workflows/diff/types'
|
||||
@@ -78,6 +79,7 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
|
||||
? currentBlock.is_diff
|
||||
: undefined
|
||||
|
||||
const isEnabled = currentBlock?.enabled ?? true
|
||||
const isPreview = data?.isPreview || false
|
||||
|
||||
// Focus state
|
||||
@@ -184,14 +186,21 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
|
||||
<div className='flex min-w-0 flex-1 items-center gap-[10px]'>
|
||||
<div
|
||||
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
|
||||
style={{ backgroundColor: blockIconBg }}
|
||||
style={{ backgroundColor: isEnabled ? blockIconBg : 'gray' }}
|
||||
>
|
||||
<BlockIcon className='h-[16px] w-[16px] text-white' />
|
||||
</div>
|
||||
<span className='font-medium text-[16px]' title={blockName}>
|
||||
<span
|
||||
className={cn(
|
||||
'truncate font-medium text-[16px]',
|
||||
!isEnabled && 'text-[var(--text-muted)]'
|
||||
)}
|
||||
title={blockName}
|
||||
>
|
||||
{blockName}
|
||||
</span>
|
||||
</div>
|
||||
{!isEnabled && <Badge variant='gray-secondary'>disabled</Badge>}
|
||||
</div>
|
||||
|
||||
{!isPreview && (
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator'
|
||||
import { SYSTEM_REFERENCE_PREFIXES } from '@/lib/workflows/sanitization/references'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import { normalizeName } from '@/executor/constants'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||
@@ -27,7 +27,7 @@ export function useAccessibleReferencePrefixes(blockId?: string | null): Set<str
|
||||
const accessibleIds = new Set<string>(ancestorIds)
|
||||
accessibleIds.add(blockId)
|
||||
|
||||
const starterBlock = Object.values(blocks).find((block) => isValidStartBlockType(block.type))
|
||||
const starterBlock = Object.values(blocks).find((block) => isInputDefinitionTrigger(block.type))
|
||||
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
|
||||
accessibleIds.add(starterBlock.id)
|
||||
}
|
||||
|
||||
@@ -180,6 +180,21 @@ function mapEdgesByNode(edges: Edge[], nodeIds: Set<string>): Map<string, Edge[]
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the panel editor with the current selection state.
|
||||
* Shows block details when exactly one block is selected, clears otherwise.
|
||||
*/
|
||||
function syncPanelWithSelection(selectedIds: string[]) {
|
||||
const { currentBlockId, clearCurrentBlock, setCurrentBlockId } = usePanelEditorStore.getState()
|
||||
if (selectedIds.length === 1 && selectedIds[0] !== currentBlockId) {
|
||||
setCurrentBlockId(selectedIds[0])
|
||||
} else if (selectedIds.length === 0 && currentBlockId) {
|
||||
clearCurrentBlock()
|
||||
} else if (selectedIds.length > 1 && currentBlockId) {
|
||||
clearCurrentBlock()
|
||||
}
|
||||
}
|
||||
|
||||
/** Custom node types for ReactFlow. */
|
||||
const nodeTypes: NodeTypes = {
|
||||
workflowBlock: WorkflowBlock,
|
||||
@@ -2075,7 +2090,10 @@ const WorkflowContent = React.memo(() => {
|
||||
...node,
|
||||
selected: pendingSet.has(node.id),
|
||||
}))
|
||||
setDisplayNodes(resolveParentChildSelectionConflicts(withSelection, blocks))
|
||||
const resolved = resolveParentChildSelectionConflicts(withSelection, blocks)
|
||||
setDisplayNodes(resolved)
|
||||
const selectedIds = resolved.filter((node) => node.selected).map((node) => node.id)
|
||||
syncPanelWithSelection(selectedIds)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2175,13 +2193,7 @@ const WorkflowContent = React.memo(() => {
|
||||
})
|
||||
const selectedIds = selectedIdsRef.current as string[] | null
|
||||
if (selectedIds !== null) {
|
||||
const { currentBlockId, clearCurrentBlock, setCurrentBlockId } =
|
||||
usePanelEditorStore.getState()
|
||||
if (selectedIds.length === 1 && selectedIds[0] !== currentBlockId) {
|
||||
setCurrentBlockId(selectedIds[0])
|
||||
} else if (selectedIds.length === 0 && currentBlockId) {
|
||||
clearCurrentBlock()
|
||||
}
|
||||
syncPanelWithSelection(selectedIds)
|
||||
}
|
||||
},
|
||||
[blocks]
|
||||
|
||||
@@ -214,6 +214,15 @@ export const A2ABlock: BlockConfig<A2AResponse> = {
|
||||
],
|
||||
config: {
|
||||
tool: (params) => params.operation as string,
|
||||
params: (params) => {
|
||||
const { fileUpload, fileReference, ...rest } = params
|
||||
const hasFileUpload = Array.isArray(fileUpload) ? fileUpload.length > 0 : !!fileUpload
|
||||
const files = hasFileUpload ? fileUpload : fileReference
|
||||
return {
|
||||
...rest,
|
||||
...(files ? { files } : {}),
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
|
||||
@@ -25,7 +25,7 @@ const badgeVariants = cva(
|
||||
orange: `${STATUS_BASE} bg-[#fed7aa] text-[#c2410c] dark:bg-[rgba(249,115,22,0.2)] dark:text-[#fdba74]`,
|
||||
amber: `${STATUS_BASE} bg-[#fde68a] text-[#a16207] dark:bg-[rgba(245,158,11,0.2)] dark:text-[#fcd34d]`,
|
||||
teal: `${STATUS_BASE} bg-[#99f6e4] text-[#0f766e] dark:bg-[rgba(20,184,166,0.2)] dark:text-[#5eead4]`,
|
||||
cyan: `${STATUS_BASE} bg-[#a5f3fc] text-[#0e7490] dark:bg-[rgba(14,165,233,0.2)] dark:text-[#7dd3fc]`,
|
||||
cyan: `${STATUS_BASE} bg-[var(--surface-4)] text-[#0891b2] dark:bg-[rgba(14,165,233,0.2)] dark:text-[#7dd3fc]`,
|
||||
'gray-secondary': `${STATUS_BASE} bg-[var(--surface-4)] text-[var(--text-secondary)]`,
|
||||
},
|
||||
size: {
|
||||
|
||||
287
apps/sim/emails/broadcasts/v0.5.html
Normal file
287
apps/sim/emails/broadcasts/v0.5.html
Normal file
@@ -0,0 +1,287 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>What's New at Sim</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background-color:#ffffff;">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" border="0" role="presentation" style="background-color:#ffffff;">
|
||||
<tr>
|
||||
<td align="center" style="padding:0 16px;">
|
||||
<table width="600" cellspacing="0" cellpadding="0" border="0" role="presentation" style="max-width:600px;width:100%;">
|
||||
|
||||
<!-- Logo -->
|
||||
<tr>
|
||||
<td align="center" style="padding-top:32px;padding-bottom:16px;">
|
||||
<a href="https://sim.ai" style="color:#000;text-decoration:none;" target="_blank">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/logo.png" width="79" alt="Sim Logo" style="display:block;width:79px;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Intro Paragraph -->
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:20px;padding-top:8px;text-align:left;">
|
||||
<p style="margin:0;">We're excited to introduce <strong>Sim v0.5</strong>, the next evolution of our agent workflow platform—built for teams who need <strong>context-aware AI assistance</strong>, <strong>seamless tool deployment</strong>, and <strong>full execution observability</strong> in production.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- CTA Button -->
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom:25px;padding-top:5px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center" style="background-color:#32bd7e;border-radius:5px;">
|
||||
<a href="https://sim.ai" style="display:inline-block;font-weight:500;font-size:14px;padding:7px 16px;text-decoration:none;color:#ffffff;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;" target="_blank">Try Sim</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Header Text -->
|
||||
<tr>
|
||||
<td align="left" valign="top" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;word-break:break-word;padding-top:10px;padding-bottom:8px;text-align:left;">
|
||||
<h2 style="margin:0;color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:28px;font-weight:600;word-break:break-word;">Get things done <em>faster</em></h2>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:30px;text-align:left;">
|
||||
<p style="margin:0;">You're focused on the most critical tasks, let agents handle the rest.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- FEATURE 1: Copilot -->
|
||||
<tr>
|
||||
<td align="left" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:18px;word-break:break-word;padding-top:10px;padding-bottom:12px;text-align:left;">
|
||||
<strong>Copilot</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="border-radius:8px;font-size:0;line-height:0;">
|
||||
<a href="https://sim.ai" style="color:#000;text-decoration:none;" target="_blank">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/copilot.jpg" width="570" alt="Sim Copilot" style="display:block;width:100%;border-radius:8px;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:25px;padding-top:12px;text-align:left;">
|
||||
<p style="margin:0;">Copilot is a context-aware assistant embedded directly into the Sim editor. It has first-class access to your workflows, blocks, execution logs, and documentation—helping you build, debug, and optimize without leaving the canvas. Ask it to explain a workflow, propose changes, or execute actions. All writes are gated behind explicit approval, so you stay in control.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- FEATURE 2: MCP Deployment -->
|
||||
<tr>
|
||||
<td align="left" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:18px;word-break:break-word;padding-top:20px;padding-bottom:12px;text-align:left;">
|
||||
<strong>MCP Deployment</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://sim.ai" style="text-decoration:none;display:block;" target="_blank">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation" width="100%" style="background-color:#181C1E;border-radius:8px;">
|
||||
<tr>
|
||||
<td align="center" style="padding:40px 30px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom:16px;">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/mcp.png" width="48" height="48" alt="MCP" style="display:block;" border="0">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<p style="margin:0;color:#ffffff;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:20px;font-weight:500;">Expose workflows as tools for any agent</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:25px;padding-top:12px;text-align:left;">
|
||||
<p style="margin:0;">Deploy any workflow as an MCP tool and expose it to any MCP-compatible agent—Claude Desktop, Cursor, or your own applications. Define custom tool names, descriptions, and parameter schemas. Your workflows become reusable building blocks that other agents can invoke, turning Sim into the backend for your agentic systems.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- FEATURE 3: Logs & Dashboard -->
|
||||
<tr>
|
||||
<td align="left" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:18px;word-break:break-word;padding-top:20px;padding-bottom:12px;text-align:left;">
|
||||
<strong>Logs & Dashboard</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="border-radius:8px;font-size:0;line-height:0;">
|
||||
<a href="https://sim.ai" style="color:#000;text-decoration:none;" target="_blank">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/dashboard.jpg" width="570" alt="Logs & Dashboard" style="display:block;width:100%;border-radius:8px;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:25px;padding-top:12px;text-align:left;">
|
||||
<p style="margin:0;">Full observability for every execution. Trace spans show you exactly what happened at each step—durations, token usage, cost breakdowns by model, and error details when things go wrong. Filter by time range, trigger type, or workflow. Live mode auto-refreshes every 5 seconds. Restore any previous workflow state with execution snapshots.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- FEATURE 4: Collaboration -->
|
||||
<tr>
|
||||
<td align="left" style="color:#2d2d2d;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:18px;word-break:break-word;padding-top:20px;padding-bottom:12px;text-align:left;">
|
||||
<strong>Realtime Collaboration</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="border-radius:8px;font-size:0;line-height:0;">
|
||||
<a href="https://sim.ai" style="color:#000;text-decoration:none;" target="_blank">
|
||||
<img src="https://sim.ai/email/broadcast/v0.5/collaboration.jpg" width="570" alt="Realtime Collaboration" style="display:block;width:100%;border-radius:8px;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:25px;padding-top:12px;text-align:left;">
|
||||
<p style="margin:0;">Invite teammates to your workspace and build workflows together in realtime—like Figma for AI agents. See cursors move, blocks added, and configurations updated as they happen. The operation queue ensures reliability even under poor network conditions. Undo/redo works per-user, so your changes stay separate from your collaborators'.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom:10px;padding-top:10px;">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" border="0" role="presentation" height="1" style="border-top:1px solid #ededed;">
|
||||
<tr>
|
||||
<td height="0" style="font-size:0;line-height:0;">­</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Final CTA -->
|
||||
<tr>
|
||||
<td align="left" style="color:#404040;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;font-size:16px;line-height:24px;word-break:break-word;padding-bottom:15px;padding-top:15px;text-align:left;">
|
||||
<p style="margin:0;">Ready to build? These features are available now in Sim.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom:30px;padding-top:15px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center" style="background-color:#32bd7e;border-radius:5px;">
|
||||
<a href="https://sim.ai" style="display:inline-block;font-weight:500;font-size:14px;padding:7px 16px;text-decoration:none;color:#ffffff;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;" target="_blank">Get Started</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Divider -->
|
||||
<tr>
|
||||
<td align="center" style="padding-top:20px;padding-bottom:20px;">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" border="0" role="presentation" height="1" style="border-top:1px solid #ededed;">
|
||||
<tr>
|
||||
<td height="0" style="font-size:0;line-height:0;">­</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Social links row -->
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table cellspacing="0" cellpadding="0" border="0" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="padding:0 8px 0 0;">
|
||||
<a href="https://sim.ai/x" rel="noopener noreferrer" target="_blank">
|
||||
<img src="https://sim.ai/static/x-icon.png" width="20" height="20" alt="X" style="display:block;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="padding:0 8px;">
|
||||
<a href="https://sim.ai/discord" rel="noopener noreferrer" target="_blank">
|
||||
<img src="https://sim.ai/static/discord-icon.png" width="20" height="20" alt="Discord" style="display:block;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="padding:0 8px;">
|
||||
<a href="https://sim.ai/github" rel="noopener noreferrer" target="_blank">
|
||||
<img src="https://sim.ai/static/github-icon.png" width="20" height="20" alt="GitHub" style="display:block;" border="0">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Spacer -->
|
||||
<tr>
|
||||
<td height="16" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Address row -->
|
||||
<tr>
|
||||
<td align="center" style="font-size:12px;line-height:20px;color:#737373;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;margin:0;">
|
||||
Sim, 80 Langton St, San Francisco, CA 94103, USA
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Spacer -->
|
||||
<tr>
|
||||
<td height="8" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Contact row -->
|
||||
<tr>
|
||||
<td align="center" style="font-size:12px;line-height:20px;color:#737373;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;margin:0;">
|
||||
Questions? <a href="mailto:support@sim.ai" style="color:#737373;text-decoration:underline;font-weight:normal;">support@sim.ai</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Spacer -->
|
||||
<tr>
|
||||
<td height="8" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Links row -->
|
||||
<tr>
|
||||
<td align="center" style="font-size:12px;line-height:20px;color:#737373;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;margin:0;">
|
||||
<a href="https://sim.ai/privacy" style="color:#737373;text-decoration:underline;font-weight:normal;" rel="noopener noreferrer" target="_blank">Privacy Policy</a>
|
||||
•
|
||||
<a href="https://sim.ai/terms" style="color:#737373;text-decoration:underline;font-weight:normal;" rel="noopener noreferrer" target="_blank">Terms of Service</a>
|
||||
•
|
||||
<a href="{{{RESEND_UNSUBSCRIBE_URL}}}" style="color:#737373;text-decoration:underline;font-weight:normal;" rel="noopener noreferrer" target="_blank">Unsubscribe</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Spacer -->
|
||||
<tr>
|
||||
<td height="16" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Copyright row -->
|
||||
<tr>
|
||||
<td align="center" style="font-size:12px;line-height:20px;color:#737373;font-family:-apple-system,'SF Pro Display','SF Pro Text','Helvetica',sans-serif;margin:0;">
|
||||
© 2026 Sim, All Rights Reserved
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Bottom spacer -->
|
||||
<tr>
|
||||
<td height="32" style="font-size:1px;line-height:1px;"> </td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
type GetBlockUpstreamReferencesResultType,
|
||||
} from '@/lib/copilot/tools/shared/schemas'
|
||||
import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||
@@ -141,7 +141,7 @@ export class GetBlockUpstreamReferencesClientTool extends BaseClientTool {
|
||||
const accessibleIds = new Set<string>(ancestorIds)
|
||||
accessibleIds.add(blockId)
|
||||
|
||||
const starterBlock = Object.values(blocks).find((b) => isValidStartBlockType(b.type))
|
||||
const starterBlock = Object.values(blocks).find((b) => isInputDefinitionTrigger(b.type))
|
||||
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
|
||||
accessibleIds.add(starterBlock.id)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import dns from 'dns/promises'
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
import type { LookupFunction } from 'net'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import * as ipaddr from 'ipaddr.js'
|
||||
|
||||
@@ -907,26 +908,28 @@ export async function secureFetchWithPinnedIP(
|
||||
const isIPv6 = resolvedIP.includes(':')
|
||||
const family = isIPv6 ? 6 : 4
|
||||
|
||||
const agentOptions = {
|
||||
lookup: (
|
||||
_hostname: string,
|
||||
_options: unknown,
|
||||
callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void
|
||||
) => {
|
||||
const lookup: LookupFunction = (_hostname, options, callback) => {
|
||||
if (options.all) {
|
||||
callback(null, [{ address: resolvedIP, family }])
|
||||
} else {
|
||||
callback(null, resolvedIP, family)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const agent = isHttps
|
||||
? new https.Agent(agentOptions as https.AgentOptions)
|
||||
: new http.Agent(agentOptions as http.AgentOptions)
|
||||
const agentOptions: http.AgentOptions = { lookup }
|
||||
|
||||
const agent = isHttps ? new https.Agent(agentOptions) : new http.Agent(agentOptions)
|
||||
|
||||
// Remove accept-encoding since Node.js http/https doesn't auto-decompress
|
||||
// Headers are lowercase due to Web Headers API normalization in executeToolRequest
|
||||
const { 'accept-encoding': _, ...sanitizedHeaders } = options.headers ?? {}
|
||||
|
||||
const requestOptions: http.RequestOptions = {
|
||||
hostname: parsed.hostname,
|
||||
port,
|
||||
path: parsed.pathname + parsed.search,
|
||||
method: options.method || 'GET',
|
||||
headers: options.headers || {},
|
||||
headers: sanitizedHeaders,
|
||||
agent,
|
||||
timeout: options.timeout || 30000,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import type { InputFormatField } from '@/lib/workflows/types'
|
||||
import type { McpToolSchema } from './types'
|
||||
|
||||
@@ -217,7 +217,7 @@ export function extractInputFormatFromBlocks(
|
||||
const blockObj = block as Record<string, unknown>
|
||||
const blockType = blockObj.type as string
|
||||
|
||||
if (isValidStartBlockType(blockType)) {
|
||||
if (isInputDefinitionTrigger(blockType)) {
|
||||
// Try to get inputFormat from subBlocks.inputFormat.value
|
||||
const subBlocks = blockObj.subBlocks as Record<string, { value?: unknown }> | undefined
|
||||
const subBlockValue = subBlocks?.inputFormat?.value
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import type { InputFormatField } from '@/lib/workflows/types'
|
||||
|
||||
/**
|
||||
@@ -25,7 +25,7 @@ export function extractInputFieldsFromBlocks(
|
||||
// Find trigger block
|
||||
const triggerEntry = Object.entries(blocks).find(([, block]) => {
|
||||
const b = block as Record<string, unknown>
|
||||
return typeof b.type === 'string' && isValidStartBlockType(b.type)
|
||||
return typeof b.type === 'string' && isInputDefinitionTrigger(b.type)
|
||||
})
|
||||
|
||||
if (!triggerEntry) return []
|
||||
|
||||
@@ -376,6 +376,7 @@ describe('Database Helpers', () => {
|
||||
forEachItems: '',
|
||||
doWhileCondition: '',
|
||||
whileCondition: '',
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
expect(result?.parallels['parallel-1']).toEqual({
|
||||
@@ -384,6 +385,7 @@ describe('Database Helpers', () => {
|
||||
count: 5,
|
||||
distribution: ['item1', 'item2'],
|
||||
parallelType: 'count',
|
||||
enabled: true,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -273,6 +273,7 @@ export async function loadWorkflowFromNormalizedTables(
|
||||
forEachItems: (config as Loop).forEachItems ?? '',
|
||||
whileCondition: (config as Loop).whileCondition ?? '',
|
||||
doWhileCondition: (config as Loop).doWhileCondition ?? '',
|
||||
enabled: migratedBlocks[subflow.id]?.enabled ?? true,
|
||||
}
|
||||
loops[subflow.id] = loop
|
||||
|
||||
@@ -301,6 +302,7 @@ export async function loadWorkflowFromNormalizedTables(
|
||||
(config as Parallel).parallelType === 'collection'
|
||||
? (config as Parallel).parallelType
|
||||
: 'count',
|
||||
enabled: migratedBlocks[subflow.id]?.enabled ?? true,
|
||||
}
|
||||
parallels[subflow.id] = parallel
|
||||
} else {
|
||||
|
||||
28
apps/sim/lib/workflows/triggers/input-definition-triggers.ts
Normal file
28
apps/sim/lib/workflows/triggers/input-definition-triggers.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Trigger types that define workflow input parameters (inputFormat).
|
||||
* These are triggers where users can configure input schema for the workflow.
|
||||
*
|
||||
* This module is kept lightweight with no dependencies to avoid circular imports.
|
||||
*
|
||||
* Note: External triggers like webhook/schedule are NOT included here because
|
||||
* they receive input from external event payloads, not user-defined inputFormat.
|
||||
*/
|
||||
export const INPUT_DEFINITION_TRIGGER_TYPES = [
|
||||
'starter',
|
||||
'start',
|
||||
'start_trigger',
|
||||
'api_trigger',
|
||||
'input_trigger',
|
||||
] as const
|
||||
|
||||
export type InputDefinitionTriggerType = (typeof INPUT_DEFINITION_TRIGGER_TYPES)[number]
|
||||
|
||||
/**
|
||||
* Check if a block type is a trigger that defines workflow input parameters.
|
||||
* Used to find blocks that have inputFormat subblock for workflow input schema.
|
||||
*/
|
||||
export function isInputDefinitionTrigger(
|
||||
blockType: string
|
||||
): blockType is InputDefinitionTriggerType {
|
||||
return INPUT_DEFINITION_TRIGGER_TYPES.includes(blockType as InputDefinitionTriggerType)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Valid start block types that can trigger a workflow
|
||||
* This module is kept lightweight with no dependencies to avoid circular imports
|
||||
*/
|
||||
export const VALID_START_BLOCK_TYPES = [
|
||||
'starter',
|
||||
'start',
|
||||
'start_trigger',
|
||||
'api',
|
||||
'api_trigger',
|
||||
'input_trigger',
|
||||
] as const
|
||||
|
||||
export type ValidStartBlockType = (typeof VALID_START_BLOCK_TYPES)[number]
|
||||
|
||||
/**
|
||||
* Check if a block type is a valid start block type
|
||||
*/
|
||||
export function isValidStartBlockType(blockType: string): blockType is ValidStartBlockType {
|
||||
return VALID_START_BLOCK_TYPES.includes(blockType as ValidStartBlockType)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import {
|
||||
type StartBlockCandidate,
|
||||
StartBlockPath,
|
||||
@@ -22,7 +22,7 @@ export function hasValidStartBlockInState(state: WorkflowState | null | undefine
|
||||
|
||||
const startBlock = Object.values(state.blocks).find((block: BlockState) => {
|
||||
const blockType = block?.type
|
||||
return isValidStartBlockType(blockType)
|
||||
return isInputDefinitionTrigger(blockType)
|
||||
})
|
||||
|
||||
return !!startBlock
|
||||
|
||||
BIN
apps/sim/public/email/broadcast/v0.5/collaboration.jpg
Normal file
BIN
apps/sim/public/email/broadcast/v0.5/collaboration.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
apps/sim/public/email/broadcast/v0.5/copilot.jpg
Normal file
BIN
apps/sim/public/email/broadcast/v0.5/copilot.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
apps/sim/public/email/broadcast/v0.5/dashboard.jpg
Normal file
BIN
apps/sim/public/email/broadcast/v0.5/dashboard.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
BIN
apps/sim/public/email/broadcast/v0.5/logo.png
Normal file
BIN
apps/sim/public/email/broadcast/v0.5/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
BIN
apps/sim/public/email/broadcast/v0.5/mcp.png
Normal file
BIN
apps/sim/public/email/broadcast/v0.5/mcp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
@@ -172,7 +172,14 @@ export type TimeRange =
|
||||
| 'All time'
|
||||
| 'Custom range'
|
||||
|
||||
export type LogLevel = 'error' | 'info' | 'running' | 'pending' | 'all' | (string & {})
|
||||
export type LogLevel =
|
||||
| 'error'
|
||||
| 'info'
|
||||
| 'running'
|
||||
| 'pending'
|
||||
| 'cancelled'
|
||||
| 'all'
|
||||
| (string & {})
|
||||
/** Core trigger types for workflow execution */
|
||||
export const CORE_TRIGGER_TYPES = [
|
||||
'manual',
|
||||
|
||||
@@ -130,6 +130,7 @@ export interface Loop {
|
||||
forEachItems?: any[] | Record<string, any> | string // Items or expression
|
||||
whileCondition?: string // JS expression that evaluates to boolean (for while loops)
|
||||
doWhileCondition?: string // JS expression that evaluates to boolean (for do-while loops)
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface Parallel {
|
||||
@@ -138,6 +139,7 @@ export interface Parallel {
|
||||
distribution?: any[] | Record<string, any> | string // Items or expression
|
||||
count?: number // Number of parallel executions for count-based parallel
|
||||
parallelType?: 'count' | 'collection' // Explicit parallel type to avoid inference bugs
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface Variable {
|
||||
|
||||
@@ -72,6 +72,7 @@ export function convertLoopBlockToLoop(
|
||||
nodes: findChildNodes(loopBlockId, blocks),
|
||||
iterations: loopBlock.data?.count || DEFAULT_LOOP_ITERATIONS,
|
||||
loopType,
|
||||
enabled: loopBlock.enabled,
|
||||
}
|
||||
|
||||
loop.forEachItems = loopBlock.data?.collection || ''
|
||||
@@ -113,6 +114,7 @@ export function convertParallelBlockToParallel(
|
||||
distribution,
|
||||
count,
|
||||
parallelType: validatedParallelType,
|
||||
enabled: parallelBlock.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user