improvement(block-outputs): fix chat fields being hidden even if in inputFormat, cleanup code (#1819)

* improvement(block-outputs): fix chat fields being hidden even if in inputFormat, cleanup code"

* fix type error
This commit is contained in:
Vikhyath Mondreti
2025-11-05 16:05:39 -08:00
committed by GitHub
parent 2b78b5ea73
commit 5c611c6d65
12 changed files with 276 additions and 217 deletions
@@ -46,7 +46,13 @@ const TYPE_CONFIG: Record<VariableType, { icon: string; placeholder: string }> =
string: { icon: 'Abc', placeholder: 'Plain text value' },
}
const VARIABLE_TYPES: VariableType[] = ['plain', 'number', 'boolean', 'object', 'array']
const VARIABLE_TYPES: Exclude<VariableType, 'string'>[] = [
'plain',
'number',
'boolean',
'object',
'array',
]
export function Variables() {
const { activeWorkflowId } = useWorkflowRegistry()
@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { LoopType, ParallelType } from '@/lib/workflows/types'
// Mock hooks
const mockCollaborativeUpdates = {
@@ -170,8 +171,6 @@ describe('IterationBadges', () => {
describe('Count Mode Detection', () => {
it.concurrent('should be in count mode for loop + for combination', () => {
type IterationType = 'loop' | 'parallel'
type LoopType = 'for' | 'forEach'
type ParallelType = 'count' | 'collection'
const iterationType: IterationType = 'loop'
const currentType: LoopType = 'for'
@@ -182,7 +181,6 @@ describe('IterationBadges', () => {
it.concurrent('should be in count mode for parallel + count combination', () => {
type IterationType = 'loop' | 'parallel'
type ParallelType = 'count' | 'collection'
const iterationType: IterationType = 'parallel'
const currentType: ParallelType = 'count'
@@ -12,9 +12,9 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import 'prismjs/components/prism-javascript'
import 'prismjs/themes/prism.css'
import type { LoopType, ParallelType } from '@/lib/workflows/types'
type IterationType = 'loop' | 'parallel'
type LoopType = 'for' | 'forEach' | 'while' | 'doWhile'
type ParallelType = 'count' | 'collection'
interface IterationNodeData {
width?: number
@@ -4,14 +4,10 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { cn } from '@/lib/utils'
import type { InputFormatField } from '@/lib/workflows/types'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
interface InputFormatField {
name: string
type?: string
}
interface InputTriggerBlock {
type: 'input_trigger' | 'start_trigger'
subBlocks?: {
@@ -42,7 +38,10 @@ function isStarterBlock(value: unknown): value is StarterBlockLegacy {
return !!value && typeof value === 'object' && (value as { type?: unknown }).type === 'starter'
}
function isInputFormatField(value: unknown): value is InputFormatField {
type ValidatedInputFormatField = Required<Pick<InputFormatField, 'name'>> &
Pick<InputFormatField, 'type'>
function isInputFormatField(value: unknown): value is ValidatedInputFormatField {
if (typeof value !== 'object' || value === null) return false
if (!('name' in value)) return false
const { name, type } = value as { name: unknown; type?: unknown }
@@ -11,6 +11,7 @@ import { createLogger } from '@/lib/logs/console/logger'
import { parseCronToHumanReadable } from '@/lib/schedules/utils'
import { cn, validateName } from '@/lib/utils'
import { type DiffStatus, hasDiffStatus } from '@/lib/workflows/diff/types'
import { TRIGGER_TYPES } from '@/lib/workflows/triggers'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import type { BlockConfig, SubBlockConfig } from '@/blocks/types'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
@@ -228,9 +229,9 @@ export const WorkflowBlock = memo(
const params = useParams()
const currentWorkflowId = params.workflowId as string
// Check if this is a starter block or trigger block
const isStarterBlock = type === 'starter'
const isWebhookTriggerBlock = type === 'webhook' || type === 'generic_webhook'
const isStarterBlock = type === TRIGGER_TYPES.STARTER
const isWebhookTriggerBlock =
type === TRIGGER_TYPES.WEBHOOK || type === TRIGGER_TYPES.GENERIC_WEBHOOK
const reactivateSchedule = async (scheduleId: string) => {
try {
+5 -4
View File
@@ -4,6 +4,7 @@ import { shallow } from 'zustand/shallow'
import { extractFieldsFromSchema, parseResponseFormatSafely } from '@/lib/response-format'
import { cn } from '@/lib/utils'
import { getBlockOutputPaths, getBlockOutputType } from '@/lib/workflows/block-outputs'
import { TRIGGER_TYPES } from '@/lib/workflows/triggers'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
import { getBlock } from '@/blocks'
import type { BlockConfig } from '@/blocks/types'
@@ -490,7 +491,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
blockTags = dynamicOutputs.map((path) => `${normalizedBlockName}.${path}`)
} else if (sourceBlock.type === 'starter') {
blockTags = [normalizedBlockName]
} else if (sourceBlock.type === 'generic_webhook') {
} else if (sourceBlock.type === TRIGGER_TYPES.GENERIC_WEBHOOK) {
blockTags = [normalizedBlockName]
} else {
blockTags = []
@@ -522,7 +523,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
}
blockTags = ensureRootTag(blockTags, normalizedBlockName)
const shouldShowRootTag = sourceBlock.type === 'generic_webhook'
const shouldShowRootTag = sourceBlock.type === TRIGGER_TYPES.GENERIC_WEBHOOK
if (!shouldShowRootTag) {
blockTags = blockTags.filter((tag) => tag !== normalizedBlockName)
}
@@ -763,7 +764,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
} else {
blockTags = [normalizedBlockName]
}
} else if (accessibleBlock.type === 'generic_webhook') {
} else if (accessibleBlock.type === TRIGGER_TYPES.GENERIC_WEBHOOK) {
blockTags = [normalizedBlockName]
} else {
blockTags = []
@@ -834,7 +835,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
}
blockTags = ensureRootTag(blockTags, normalizedBlockName)
const shouldShowRootTag = accessibleBlock.type === 'generic_webhook'
const shouldShowRootTag = accessibleBlock.type === TRIGGER_TYPES.GENERIC_WEBHOOK
if (!shouldShowRootTag) {
blockTags = blockTags.filter((tag) => tag !== normalizedBlockName)
}
+1 -6
View File
@@ -5,6 +5,7 @@ import {
resolveStartCandidates,
StartBlockPath,
} from '@/lib/workflows/triggers'
import type { InputFormatField } from '@/lib/workflows/types'
import type { NormalizedBlockOutput, UserFile } from '@/executor/types'
import type { SerializedBlock } from '@/serializer/types'
@@ -95,12 +96,6 @@ export function buildResolutionFromBlock(block: SerializedBlock): ExecutorStartR
}
}
type InputFormatField = {
name?: string
type?: string | null
value?: unknown
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
+11 -12
View File
@@ -1,6 +1,8 @@
import { v4 as uuidv4 } from 'uuid'
import { createLogger } from '@/lib/logs/console/logger'
import { uploadExecutionFile } from '@/lib/uploads/contexts/execution'
import { TRIGGER_TYPES } from '@/lib/workflows/triggers'
import type { InputFormatField } from '@/lib/workflows/types'
import type { UserFile } from '@/executor/types'
import type { SerializedBlock } from '@/serializer/types'
@@ -8,11 +10,6 @@ const logger = createLogger('ExecutionFiles')
const MAX_FILE_SIZE = 20 * 1024 * 1024 // 20MB
interface InputFormatField {
name: string
type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'files'
}
/**
* Process a single file for workflow execution - handles both base64 ('file' type) and URL pass-through ('url' type)
*/
@@ -112,7 +109,9 @@ export async function processExecutionFiles(
/**
* Extract inputFormat fields from a start block or trigger block
*/
function extractInputFormatFromBlock(block: SerializedBlock): InputFormatField[] {
type ValidatedInputFormatField = Required<Pick<InputFormatField, 'name' | 'type'>>
function extractInputFormatFromBlock(block: SerializedBlock): ValidatedInputFormatField[] {
const inputFormatValue = block.config?.params?.inputFormat
if (!Array.isArray(inputFormatValue) || inputFormatValue.length === 0) {
@@ -120,7 +119,7 @@ function extractInputFormatFromBlock(block: SerializedBlock): InputFormatField[]
}
return inputFormatValue.filter(
(field): field is InputFormatField =>
(field): field is ValidatedInputFormatField =>
field &&
typeof field === 'object' &&
'name' in field &&
@@ -148,11 +147,11 @@ export async function processInputFileFields(
const startBlock = blocks.find((block) => {
const blockType = block.metadata?.id
return (
blockType === 'start_trigger' ||
blockType === 'api_trigger' ||
blockType === 'input_trigger' ||
blockType === 'generic_webhook' ||
blockType === 'starter'
blockType === TRIGGER_TYPES.START ||
blockType === TRIGGER_TYPES.API ||
blockType === TRIGGER_TYPES.INPUT ||
blockType === TRIGGER_TYPES.GENERIC_WEBHOOK ||
blockType === TRIGGER_TYPES.STARTER
)
})
+211 -176
View File
@@ -1,9 +1,27 @@
import { classifyStartBlockType, StartBlockPath } from '@/lib/workflows/triggers'
import { classifyStartBlockType, StartBlockPath, TRIGGER_TYPES } from '@/lib/workflows/triggers'
import {
type InputFormatField,
START_BLOCK_RESERVED_FIELDS,
USER_FILE_ACCESSIBLE_PROPERTIES,
USER_FILE_PROPERTY_TYPES,
} from '@/lib/workflows/types'
import { getBlock } from '@/blocks'
import type { BlockConfig } from '@/blocks/types'
import { getTrigger, isTriggerValid } from '@/triggers'
type InputFormatField = { name?: string; type?: string | undefined | null }
type OutputDefinition = Record<string, any>
const CHAT_OUTPUTS: OutputDefinition = {
input: { type: 'string', description: 'User message' },
conversationId: { type: 'string', description: 'Conversation ID' },
files: { type: 'files', description: 'Uploaded files' },
}
const UNIFIED_START_OUTPUTS: OutputDefinition = {
input: { type: 'string', description: 'Primary user input or message' },
conversationId: { type: 'string', description: 'Conversation thread identifier' },
files: { type: 'files', description: 'User uploaded files' },
}
function normalizeInputFormatValue(inputFormatValue: any): InputFormatField[] {
if (
@@ -23,8 +41,8 @@ function normalizeInputFormatValue(inputFormatValue: any): InputFormatField[] {
function applyInputFormatFields(
inputFormat: InputFormatField[],
outputs: Record<string, any>
): Record<string, any> {
outputs: OutputDefinition
): OutputDefinition {
for (const field of inputFormat) {
const fieldName = field?.name?.trim()
if (!fieldName) continue
@@ -38,29 +56,103 @@ function applyInputFormatFields(
return outputs
}
/**
* Get the effective outputs for a block, including dynamic outputs from inputFormat
* and trigger outputs for blocks in trigger mode
*/
function hasInputFormat(blockConfig: BlockConfig): boolean {
return blockConfig.subBlocks?.some((sb) => sb.type === 'input-format') || false
}
function getTriggerId(
subBlocks: Record<string, any> | undefined,
blockConfig: BlockConfig
): string | undefined {
const selectedTriggerIdValue = subBlocks?.selectedTriggerId?.value
const triggerIdValue = subBlocks?.triggerId?.value
return (
(typeof selectedTriggerIdValue === 'string' && isTriggerValid(selectedTriggerIdValue)
? selectedTriggerIdValue
: undefined) ||
(typeof triggerIdValue === 'string' && isTriggerValid(triggerIdValue)
? triggerIdValue
: undefined) ||
blockConfig.triggers?.available?.[0]
)
}
function getUnifiedStartOutputs(subBlocks: Record<string, any> | undefined): OutputDefinition {
const outputs = { ...UNIFIED_START_OUTPUTS }
const normalizedInputFormat = normalizeInputFormatValue(subBlocks?.inputFormat?.value)
return applyInputFormatFields(normalizedInputFormat, outputs)
}
function getLegacyStarterOutputs(subBlocks: Record<string, any> | undefined): OutputDefinition {
const startWorkflowValue = subBlocks?.startWorkflow?.value
if (startWorkflowValue === 'chat') {
return { ...CHAT_OUTPUTS }
}
if (
startWorkflowValue === 'api' ||
startWorkflowValue === 'run' ||
startWorkflowValue === 'manual'
) {
const normalizedInputFormat = normalizeInputFormatValue(subBlocks?.inputFormat?.value)
return applyInputFormatFields(normalizedInputFormat, {})
}
return {}
}
function shouldClearBaseOutputs(
blockType: string,
normalizedInputFormat: InputFormatField[]
): boolean {
if (blockType === TRIGGER_TYPES.API || blockType === TRIGGER_TYPES.INPUT) {
return true
}
if (blockType === TRIGGER_TYPES.GENERIC_WEBHOOK && normalizedInputFormat.length > 0) {
return true
}
return false
}
function applyInputFormatToOutputs(
blockType: string,
blockConfig: BlockConfig,
subBlocks: Record<string, any> | undefined,
baseOutputs: OutputDefinition
): OutputDefinition {
if (!hasInputFormat(blockConfig) || !subBlocks?.inputFormat?.value) {
return baseOutputs
}
const normalizedInputFormat = normalizeInputFormatValue(subBlocks.inputFormat.value)
if (!Array.isArray(subBlocks.inputFormat.value)) {
if (blockType === TRIGGER_TYPES.API || blockType === TRIGGER_TYPES.INPUT) {
return {}
}
return baseOutputs
}
const shouldClear = shouldClearBaseOutputs(blockType, normalizedInputFormat)
const outputs = shouldClear ? {} : { ...baseOutputs }
return applyInputFormatFields(normalizedInputFormat, outputs)
}
export function getBlockOutputs(
blockType: string,
subBlocks?: Record<string, any>,
triggerMode?: boolean
): Record<string, any> {
): OutputDefinition {
const blockConfig = getBlock(blockType)
if (!blockConfig) return {}
if (triggerMode && blockConfig.triggers?.enabled) {
const selectedTriggerIdValue = subBlocks?.selectedTriggerId?.value
const triggerIdValue = subBlocks?.triggerId?.value
const triggerId =
(typeof selectedTriggerIdValue === 'string' && isTriggerValid(selectedTriggerIdValue)
? selectedTriggerIdValue
: undefined) ||
(typeof triggerIdValue === 'string' && isTriggerValid(triggerIdValue)
? triggerIdValue
: undefined) ||
blockConfig.triggers?.available?.[0]
const triggerId = getTriggerId(subBlocks, blockConfig)
if (triggerId && isTriggerValid(triggerId)) {
const trigger = getTrigger(triggerId)
if (trigger.outputs) {
@@ -69,155 +161,128 @@ export function getBlockOutputs(
}
}
let outputs = { ...(blockConfig.outputs || {}) }
const startPath = classifyStartBlockType(blockType)
if (startPath === StartBlockPath.UNIFIED) {
outputs = {
input: { type: 'string', description: 'Primary user input or message' },
conversationId: { type: 'string', description: 'Conversation thread identifier' },
files: { type: 'files', description: 'User uploaded files' },
}
const normalizedInputFormat = normalizeInputFormatValue(subBlocks?.inputFormat?.value)
for (const field of normalizedInputFormat) {
const fieldName = field?.name?.trim()
if (!fieldName) continue
outputs[fieldName] = {
type: (field?.type || 'any') as any,
description: `Field from input format`,
}
}
return outputs
return getUnifiedStartOutputs(subBlocks)
}
// Special handling for starter block (legacy)
if (startPath === StartBlockPath.LEGACY_STARTER) {
const startWorkflowValue = subBlocks?.startWorkflow?.value
if (startWorkflowValue === 'chat') {
// Chat mode outputs
return {
input: { type: 'string', description: 'User message' },
conversationId: { type: 'string', description: 'Conversation ID' },
files: { type: 'files', description: 'Uploaded files' },
}
}
if (
startWorkflowValue === 'api' ||
startWorkflowValue === 'run' ||
startWorkflowValue === 'manual'
) {
// API/manual mode - use inputFormat fields only
const normalizedInputFormat = normalizeInputFormatValue(subBlocks?.inputFormat?.value)
outputs = {}
return applyInputFormatFields(normalizedInputFormat, outputs)
}
return getLegacyStarterOutputs(subBlocks)
}
// For blocks with inputFormat, add dynamic outputs
if (hasInputFormat(blockConfig) && subBlocks?.inputFormat?.value) {
const normalizedInputFormat = normalizeInputFormatValue(subBlocks.inputFormat.value)
const baseOutputs = { ...(blockConfig.outputs || {}) }
return applyInputFormatToOutputs(blockType, blockConfig, subBlocks, baseOutputs)
}
if (!Array.isArray(subBlocks.inputFormat.value)) {
if (blockType === 'api_trigger' || blockType === 'input_trigger') {
outputs = {}
function shouldFilterReservedField(
blockType: string,
key: string,
prefix: string,
subBlocks: Record<string, any> | undefined
): boolean {
if (blockType !== TRIGGER_TYPES.START || prefix) {
return false
}
if (!START_BLOCK_RESERVED_FIELDS.includes(key as any)) {
return false
}
const normalizedInputFormat = normalizeInputFormatValue(subBlocks?.inputFormat?.value)
const isExplicitlyDefined = normalizedInputFormat.some((field) => field?.name?.trim() === key)
return !isExplicitlyDefined
}
function expandFileTypeProperties(path: string): string[] {
return USER_FILE_ACCESSIBLE_PROPERTIES.map((prop) => `${path}.${prop}`)
}
function collectOutputPaths(
obj: OutputDefinition,
blockType: string,
subBlocks: Record<string, any> | undefined,
prefix = ''
): string[] {
const paths: string[] = []
for (const [key, value] of Object.entries(obj)) {
const path = prefix ? `${prefix}.${key}` : key
if (shouldFilterReservedField(blockType, key, prefix, subBlocks)) {
continue
}
if (value && typeof value === 'object' && 'type' in value) {
if (value.type === 'files') {
paths.push(...expandFileTypeProperties(path))
} else {
paths.push(path)
}
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
paths.push(...collectOutputPaths(value, blockType, subBlocks, path))
} else {
if (
blockType === 'api_trigger' ||
blockType === 'input_trigger' ||
blockType === 'generic_webhook'
) {
if (normalizedInputFormat.length > 0 || blockType !== 'generic_webhook') {
outputs = {}
}
outputs = applyInputFormatFields(normalizedInputFormat, outputs)
}
}
if (
!Array.isArray(subBlocks.inputFormat.value) &&
(blockType === 'api_trigger' || blockType === 'input_trigger')
) {
// If no inputFormat defined, API/Input trigger has no outputs
outputs = {}
paths.push(path)
}
}
return outputs
return paths
}
/**
* Check if a block config has an inputFormat sub-block
*/
function hasInputFormat(blockConfig: BlockConfig): boolean {
return blockConfig.subBlocks?.some((sb) => sb.type === 'input-format') || false
}
/**
* Get output paths for a block (for tag dropdown)
*/
export function getBlockOutputPaths(
blockType: string,
subBlocks?: Record<string, any>,
triggerMode?: boolean
): string[] {
const outputs = getBlockOutputs(blockType, subBlocks, triggerMode)
return collectOutputPaths(outputs, blockType, subBlocks)
}
// Recursively collect all paths from nested outputs
const paths: string[] = []
function collectPaths(obj: Record<string, any>, prefix = ''): void {
for (const [key, value] of Object.entries(obj)) {
const path = prefix ? `${prefix}.${key}` : key
// For start_trigger, skip reserved fields at root level (they're always present but hidden from dropdown)
if (
blockType === 'start_trigger' &&
!prefix &&
['input', 'conversationId', 'files'].includes(key)
) {
continue
}
// If value has 'type' property, it's a leaf node (output definition)
if (value && typeof value === 'object' && 'type' in value) {
// Special handling for 'files' type - expand to show array element properties
if (value.type === 'files') {
// Show properties without [0] for cleaner display
// The tag dropdown will add [0] automatically when inserting
// Only expose user-accessible fields (matches UserFile interface)
paths.push(`${path}.id`)
paths.push(`${path}.name`)
paths.push(`${path}.url`)
paths.push(`${path}.size`)
paths.push(`${path}.type`)
} else {
paths.push(path)
}
}
// If value is an object without 'type', recurse into it
else if (value && typeof value === 'object' && !Array.isArray(value)) {
collectPaths(value, path)
}
// Otherwise treat as a leaf node
else {
paths.push(path)
}
function getFilePropertyType(outputs: OutputDefinition, pathParts: string[]): string | null {
const lastPart = pathParts[pathParts.length - 1]
if (!lastPart || !USER_FILE_PROPERTY_TYPES[lastPart as keyof typeof USER_FILE_PROPERTY_TYPES]) {
return null
}
let current: any = outputs
for (const part of pathParts.slice(0, -1)) {
if (!current || typeof current !== 'object') {
return null
}
current = current[part]
}
if (current && typeof current === 'object' && 'type' in current && current.type === 'files') {
return USER_FILE_PROPERTY_TYPES[lastPart as keyof typeof USER_FILE_PROPERTY_TYPES]
}
return null
}
function traverseOutputPath(outputs: OutputDefinition, pathParts: string[]): any {
let current: any = outputs
for (const part of pathParts) {
if (!current || typeof current !== 'object') {
return null
}
current = current[part]
}
return current
}
function extractType(value: any): string {
if (!value) return 'any'
if (typeof value === 'object' && 'type' in value) {
return value.type
}
collectPaths(outputs)
return paths
return typeof value === 'string' ? value : 'any'
}
/**
* Get the type of a specific output path (supports nested paths like "email.subject")
*/
export function getBlockOutputType(
blockType: string,
outputPath: string,
@@ -226,44 +291,14 @@ export function getBlockOutputType(
): string {
const outputs = getBlockOutputs(blockType, subBlocks, triggerMode)
const arrayIndexRegex = /\[(\d+)\]/g
const cleanPath = outputPath.replace(arrayIndexRegex, '')
const cleanPath = outputPath.replace(/\[(\d+)\]/g, '')
const pathParts = cleanPath.split('.').filter(Boolean)
const filePropertyTypes: Record<string, string> = {
id: 'string',
name: 'string',
url: 'string',
size: 'number',
type: 'string',
const filePropertyType = getFilePropertyType(outputs, pathParts)
if (filePropertyType) {
return filePropertyType
}
const lastPart = pathParts[pathParts.length - 1]
if (lastPart && filePropertyTypes[lastPart]) {
let current: any = outputs
for (const part of pathParts.slice(0, -1)) {
if (!current || typeof current !== 'object') break
current = current[part]
}
if (current && typeof current === 'object' && 'type' in current && current.type === 'files') {
return filePropertyTypes[lastPart]
}
}
let current: any = outputs
for (const part of pathParts) {
if (!current || typeof current !== 'object') {
return 'any'
}
current = current[part]
}
if (!current) return 'any'
if (typeof current === 'object' && 'type' in current) {
return current.type
}
return typeof current === 'string' ? current : 'any'
const value = traverseOutputPath(outputs, pathParts)
return extractType(value)
}
+2 -2
View File
@@ -1,3 +1,4 @@
import { TRIGGER_TYPES } from '@/lib/workflows/triggers'
import { getAllBlocks, getBlock } from '@/blocks'
import type { BlockConfig } from '@/blocks/types'
@@ -99,8 +100,7 @@ export function getTriggerDisplayName(blockType: string): string {
const block = getBlock(blockType)
if (!block) return blockType
// Special case for generic_webhook - show as "Webhook" in UI
if (blockType === 'generic_webhook') {
if (blockType === TRIGGER_TYPES.GENERIC_WEBHOOK) {
return 'Webhook'
}
+2 -2
View File
@@ -9,6 +9,7 @@ export const TRIGGER_TYPES = {
CHAT: 'chat_trigger',
API: 'api_trigger',
WEBHOOK: 'webhook',
GENERIC_WEBHOOK: 'generic_webhook',
SCHEDULE: 'schedule',
START: 'start_trigger',
STARTER: 'starter', // Legacy
@@ -334,8 +335,7 @@ export class TriggerUtils {
// Use the block's actual name from the registry
const block = getBlock(triggerType)
if (block) {
// Special case for generic_webhook - show as "Webhook" in UI
if (triggerType === 'generic_webhook') {
if (triggerType === TRIGGER_TYPES.GENERIC_WEBHOOK) {
return 'Webhook'
}
return block.name
+25
View File
@@ -0,0 +1,25 @@
export interface InputFormatField {
name?: string
type?: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'files' | string
value?: unknown
}
export const USER_FILE_ACCESSIBLE_PROPERTIES = ['id', 'name', 'url', 'size', 'type'] as const
export type UserFileAccessibleProperty = (typeof USER_FILE_ACCESSIBLE_PROPERTIES)[number]
export const USER_FILE_PROPERTY_TYPES: Record<UserFileAccessibleProperty, string> = {
id: 'string',
name: 'string',
url: 'string',
size: 'number',
type: 'string',
} as const
export const START_BLOCK_RESERVED_FIELDS = ['input', 'conversationId', 'files'] as const
export type StartBlockReservedField = (typeof START_BLOCK_RESERVED_FIELDS)[number]
export type LoopType = 'for' | 'forEach' | 'while' | 'doWhile'
export type ParallelType = 'collection' | 'count'