From 2acef5c6f3d5cd9738e9167f5f7ed3b44dcee1a9 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Tue, 17 Jun 2025 18:40:31 +0000 Subject: [PATCH 01/11] feat: api response block and implementation --- .../app/api/workflows/[id]/execute/route.ts | 20 +++++- .../workflow-block/workflow-block.tsx | 2 +- apps/sim/blocks/blocks/response.ts | 70 ++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/executor/handlers/index.ts | 2 + .../handlers/response/response-handler.ts | 72 +++++++++++++++++++ apps/sim/executor/index.ts | 6 +- apps/sim/lib/webhooks/utils.ts | 29 ++++++-- apps/sim/lib/workflows/utils.ts | 35 +++++++++ apps/sim/tools/response/types.ts | 10 +++ 10 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 apps/sim/blocks/blocks/response.ts create mode 100644 apps/sim/executor/handlers/response/response-handler.ts create mode 100644 apps/sim/tools/response/types.ts diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 2e53a314f4d..c61d37f758f 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -7,7 +7,11 @@ import { persistExecutionError, persistExecutionLogs } from '@/lib/logs/executio import { buildTraceSpans } from '@/lib/logs/trace-spans' import { checkServerSideUsageLimits } from '@/lib/usage-monitor' import { decryptSecret } from '@/lib/utils' -import { updateWorkflowRunCounts } from '@/lib/workflows/utils' +import { + createHttpResponseFromBlock, + updateWorkflowRunCounts, + workflowHasResponseBlock, +} from '@/lib/workflows/utils' import { db } from '@/db' import { environment, userStats } from '@/db/schema' import { Executor } from '@/executor' @@ -304,6 +308,13 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ } const result = await executeWorkflow(validation.workflow, requestId) + + // Check if the workflow execution contains a response block output + const hasResponseBlock = workflowHasResponseBlock(result) + if (hasResponseBlock) { + return createHttpResponseFromBlock(result) + } + return createSuccessResponse(result) } catch (error: any) { logger.error(`[${requestId}] Error executing workflow: ${id}`, error) @@ -357,6 +368,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ // Execute workflow with the structured input const result = await executeWorkflow(validation.workflow, requestId, input) + + // Check if the workflow execution contains a response block output + const hasResponseBlock = workflowHasResponseBlock(result) + if (hasResponseBlock) { + return createHttpResponseFromBlock(result) + } + return createSuccessResponse(result) } catch (error: any) { logger.error(`[${requestId}] Error executing workflow: ${id}`, error) 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 89309c40810..aefb9ad97e9 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 @@ -736,7 +736,7 @@ export function WorkflowBlock({ id, data }: NodeProps) { {/* Output Handle */} - {type !== 'condition' && ( + {type !== 'condition' && type !== 'response' && ( <> = { + type: 'response', + name: 'Response', + description: 'Send a structured response back to API calls only', + longDescription: + "Transform your workflow's variables into a structured HTTP response for API calls. Define response data, status code, and headers. This is the final block in a workflow and cannot have further connections.", + docsLink: 'https://docs.simstudio.ai/blocks/response', + category: 'blocks', + bgColor: '#2F55FF', + icon: ApiIcon, + subBlocks: [ + { + id: 'data', + title: 'Response Data', + type: 'code', + layout: 'full', + placeholder: '{\n "message": "Hello world",\n "userId": ""\n}', + language: 'json', + description: + 'Data that will be sent as the response body on API calls. Use to reference workflow variables.', + }, + { + id: 'status', + title: 'Status Code', + type: 'short-input', + layout: 'half', + placeholder: '200', + description: 'HTTP status code (default: 200)', + }, + { + id: 'headers', + title: 'Response Headers', + type: 'table', + layout: 'full', + columns: ['Key', 'Value'], + description: 'Additional HTTP headers to include in the response', + }, + ], + tools: { access: [] }, + inputs: { + data: { + type: 'json', + required: false, + description: 'The JSON data to send in the response body', + }, + status: { + type: 'number', + required: false, + description: 'HTTP status code (default: 200)', + }, + headers: { + type: 'json', + required: false, + description: 'Additional response headers', + }, + }, + outputs: { + response: { + type: { + data: 'json', + status: 'number', + headers: 'json', + }, + }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 9fb874bc0a4..9b45d9c3d0f 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -45,6 +45,7 @@ import { OutlookBlock } from './blocks/outlook' import { PerplexityBlock } from './blocks/perplexity' import { PineconeBlock } from './blocks/pinecone' import { RedditBlock } from './blocks/reddit' +import { ResponseBlock } from './blocks/response' import { RouterBlock } from './blocks/router' import { S3Block } from './blocks/s3' import { SerperBlock } from './blocks/serper' @@ -128,6 +129,7 @@ export const registry: Record = { x: XBlock, youtube: YouTubeBlock, huggingface: HuggingFaceBlock, + response: ResponseBlock, } // Helper functions to access the registry diff --git a/apps/sim/executor/handlers/index.ts b/apps/sim/executor/handlers/index.ts index 51ad100c5ae..754832ce82f 100644 --- a/apps/sim/executor/handlers/index.ts +++ b/apps/sim/executor/handlers/index.ts @@ -6,6 +6,7 @@ import { FunctionBlockHandler } from './function/function-handler' import { GenericBlockHandler } from './generic/generic-handler' import { LoopBlockHandler } from './loop/loop-handler' import { ParallelBlockHandler } from './parallel/parallel-handler' +import { ResponseBlockHandler } from './response/response-handler' import { RouterBlockHandler } from './router/router-handler' import { WorkflowBlockHandler } from './workflow/workflow-handler' @@ -18,6 +19,7 @@ export { GenericBlockHandler, LoopBlockHandler, ParallelBlockHandler, + ResponseBlockHandler, RouterBlockHandler, WorkflowBlockHandler, } diff --git a/apps/sim/executor/handlers/response/response-handler.ts b/apps/sim/executor/handlers/response/response-handler.ts new file mode 100644 index 00000000000..3a0dff90ebb --- /dev/null +++ b/apps/sim/executor/handlers/response/response-handler.ts @@ -0,0 +1,72 @@ +import { createLogger } from '@/lib/logs/console-logger' +import type { BlockOutput } from '@/blocks/types' +import type { SerializedBlock } from '@/serializer/types' +import type { BlockHandler } from '../../types' + +const logger = createLogger('ResponseBlockHandler') + +export class ResponseBlockHandler implements BlockHandler { + canHandle(block: SerializedBlock): boolean { + return block.metadata?.id === 'response' + } + + async execute(block: SerializedBlock, inputs: Record): Promise { + logger.info(`Executing response block: ${block.id}`) + + try { + const responseData = inputs.data || {} + const statusCode = this.parseStatus(inputs.status) + const responseHeaders = this.parseHeaders(inputs.headers) + + logger.info('Response prepared', { + status: statusCode, + dataKeys: Object.keys(responseData), + headerKeys: Object.keys(responseHeaders), + }) + + return { + response: { + data: responseData, + status: statusCode, + headers: responseHeaders, + }, + } + } catch (error: any) { + logger.error('Response block execution failed:', error) + return { + response: { + data: { + error: 'Response block execution failed', + message: error.message || 'Unknown error', + }, + status: 500, + headers: { 'Content-Type': 'application/json' }, + }, + } + } + } + + private parseStatus(status?: string): number { + if (!status) return 200 + return Number(status) + } + + private parseHeaders( + headers: { + id: string + cells: { Key: string; Value: string } + }[] + ): Record { + const defaultHeaders = { 'Content-Type': 'application/json' } + if (!headers) return defaultHeaders + + const headerObj = headers.reduce((acc: Record, header) => { + if (header?.cells?.Key && header?.cells?.Value) { + acc[header.cells.Key] = header.cells.Value + } + return acc + }, {}) + + return { ...defaultHeaders, ...headerObj } + } +} diff --git a/apps/sim/executor/index.ts b/apps/sim/executor/index.ts index 9f29be36e23..34b3743ebcd 100644 --- a/apps/sim/executor/index.ts +++ b/apps/sim/executor/index.ts @@ -13,6 +13,7 @@ import { GenericBlockHandler, LoopBlockHandler, ParallelBlockHandler, + ResponseBlockHandler, RouterBlockHandler, WorkflowBlockHandler, } from './handlers/index' @@ -142,6 +143,7 @@ export class Executor { new ApiBlockHandler(), new LoopBlockHandler(this.resolver), new ParallelBlockHandler(this.resolver), + new ResponseBlockHandler(), new WorkflowBlockHandler(), new GenericBlockHandler(), ] @@ -312,7 +314,9 @@ export class Executor { } // Update the console UI with the final content - consoleStore.updateConsole(consoleEntryId, { output: updatedOutput }) + consoleStore.updateConsole(consoleEntryId, { + output: updatedOutput, + }) // Update the execution data itself with the final content // so that when logs are persisted, they have the complete content diff --git a/apps/sim/lib/webhooks/utils.ts b/apps/sim/lib/webhooks/utils.ts index 42369f973fd..f969d95d6d4 100644 --- a/apps/sim/lib/webhooks/utils.ts +++ b/apps/sim/lib/webhooks/utils.ts @@ -882,7 +882,9 @@ export function verifyProviderWebhook( logger.warn( `[${requestId}] Forbidden webhook access attempt - IP not allowed: ${clientIp}` ) - return new NextResponse('Forbidden - IP not allowed', { status: 403 }) + return new NextResponse('Forbidden - IP not allowed', { + status: 403, + }) } } break @@ -915,7 +917,9 @@ export async function fetchAndProcessAirtablePayloads( let apiCallCount = 0 // Use a Map to consolidate changes per record ID const consolidatedChangesMap = new Map() - const localProviderConfig = { ...((webhookData.providerConfig as Record) || {}) } // Local copy + const localProviderConfig = { + ...((webhookData.providerConfig as Record) || {}), + } // Local copy // DEBUG: Log start of function execution with critical info logger.debug(`[${requestId}] TRACE: fetchAndProcessAirtablePayloads started`, { @@ -959,7 +963,10 @@ export async function fetchAndProcessAirtablePayloads( await db .update(webhook) .set({ - providerConfig: { ...localProviderConfig, externalWebhookCursor: null }, + providerConfig: { + ...localProviderConfig, + externalWebhookCursor: null, + }, updatedAt: new Date(), }) .where(eq(webhook.id, webhookData.id)) @@ -1056,7 +1063,10 @@ export async function fetchAndProcessAirtablePayloads( const fetchStartTime = Date.now() const response = await fetch(fullUrl, { method: 'GET', - headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, }) // DEBUG: Log API response time @@ -1076,7 +1086,11 @@ export async function fetchAndProcessAirtablePayloads( `Airtable API error Status ${response.status}` logger.error( `[${requestId}] Airtable API request to /payloads failed (Call ${apiCallCount})`, - { webhookId: webhookData.id, status: response.status, error: errorMessage } + { + webhookId: webhookData.id, + status: response.status, + error: errorMessage, + } ) await persistExecutionError( workflowData.id, @@ -1206,7 +1220,10 @@ export async function fetchAndProcessAirtablePayloads( currentCursor = nextCursor // Follow exactly the old implementation - use awaited update instead of parallel - const updatedConfig = { ...localProviderConfig, externalWebhookCursor: currentCursor } + const updatedConfig = { + ...localProviderConfig, + externalWebhookCursor: currentCursor, + } try { // Force a complete object update to ensure consistency in serverless env await db diff --git a/apps/sim/lib/workflows/utils.ts b/apps/sim/lib/workflows/utils.ts index b057de8464b..5f189f00f2c 100644 --- a/apps/sim/lib/workflows/utils.ts +++ b/apps/sim/lib/workflows/utils.ts @@ -1,7 +1,9 @@ import { eq } from 'drizzle-orm' +import { NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console-logger' import { db } from '@/db' import { userStats, workflow as workflowTable } from '@/db/schema' +import type { ExecutionResult } from '@/executor/types' import type { WorkflowState } from '@/stores/workflows/workflow/types' import { env } from '../env' @@ -317,3 +319,36 @@ export function hasWorkflowChanged( export function stripCustomToolPrefix(name: string) { return name.startsWith('custom_') ? name.replace('custom_', '') : name } + +export const workflowHasResponseBlock = (executionResult: ExecutionResult): boolean => { + if ( + !executionResult?.logs || + !Array.isArray(executionResult.logs) || + !executionResult.success || + !executionResult.output.response + ) { + return false + } + + const responseBlock = executionResult.logs.find( + (log) => log?.blockType === 'response' && log?.success + ) + + return responseBlock !== undefined +} + +// Create a HTTP response from response block +export const createHttpResponseFromBlock = (executionResult: ExecutionResult): NextResponse => { + const output = executionResult.output.response + const { data = {}, status = 200, headers = {} } = output + + const responseHeaders = new Headers({ + 'Content-Type': 'application/json', + ...headers, + }) + + return NextResponse.json(data, { + status: status, + headers: responseHeaders, + }) +} diff --git a/apps/sim/tools/response/types.ts b/apps/sim/tools/response/types.ts new file mode 100644 index 00000000000..47f41e04a70 --- /dev/null +++ b/apps/sim/tools/response/types.ts @@ -0,0 +1,10 @@ +import type { ToolResponse } from '@/tools/types' + +export interface ResponseBlockOutput extends ToolResponse { + success: boolean + output: { + data: Record + status: number + headers: Record + } +} From 1f26fcc90157067c138dccc19263037c00701a20 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 18 Jun 2025 13:53:49 +0000 Subject: [PATCH 02/11] chore: enable input format again --- apps/sim/blocks/blocks/starter.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/sim/blocks/blocks/starter.ts b/apps/sim/blocks/blocks/starter.ts index 53815868305..8a5ace14869 100644 --- a/apps/sim/blocks/blocks/starter.ts +++ b/apps/sim/blocks/blocks/starter.ts @@ -32,13 +32,13 @@ export const StarterBlock: BlockConfig = { value: () => 'manual', }, // Structured Input format - visible if manual run is selected - // { - // id: 'inputFormat', - // title: 'Input Format (for API calls)', - // type: 'input-format', - // layout: 'full', - // condition: { field: 'startWorkflow', value: 'manual' }, - // }, + { + id: 'inputFormat', + title: 'Input Format (for API calls)', + type: 'input-format', + layout: 'full', + condition: { field: 'startWorkflow', value: 'manual' }, + }, // Webhook configuration { id: 'webhookProvider', From d9fb76c41a5fd236a45eff4b481b14c8bd485915 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 18 Jun 2025 14:49:42 +0000 Subject: [PATCH 03/11] fix: process the input made on api calls with proper extraction --- apps/sim/executor/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/sim/executor/index.ts b/apps/sim/executor/index.ts index 34b3743ebcd..9fc8c8e5423 100644 --- a/apps/sim/executor/index.ts +++ b/apps/sim/executor/index.ts @@ -770,11 +770,14 @@ export class Executor { */ // Handle structured input (like API calls or chat messages) if (this.workflowInput && typeof this.workflowInput === 'object') { - // Preserve complete workflowInput structure to maintain JSON format - // when referenced through + // Extract the actual input data - if workflowInput has an `input` field, use that + // Otherwise use the entire workflowInput object + const inputData = + this.workflowInput.input !== undefined ? this.workflowInput.input : this.workflowInput + const starterOutput = { response: { - input: this.workflowInput, + input: inputData, // Add top-level fields for backward compatibility message: this.workflowInput.input, conversationId: this.workflowInput.conversationId, @@ -805,9 +808,12 @@ export class Executor { logger.warn('Error processing starter block input format:', e) // Error handler fallback - preserve structure for both direct access and backward compatibility + const inputData = + this.workflowInput?.input !== undefined ? this.workflowInput.input : this.workflowInput + const starterOutput = { response: { - input: this.workflowInput, + input: inputData, message: this.workflowInput?.input, conversationId: this.workflowInput?.conversationId, }, From 93a1d99a93a53c87fe1546f021dcd6d887a217a1 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 18 Jun 2025 18:26:10 +0000 Subject: [PATCH 04/11] feat: add json-object for ai generation for response block and others --- apps/sim/app/api/codegen/route.ts | 19 +++++++++++++++++++ .../components/sub-block/components/code.tsx | 18 ++++++++++++------ .../app/w/[id]/hooks/use-code-generation.ts | 1 + apps/sim/blocks/blocks/response.ts | 5 +++-- apps/sim/blocks/types.ts | 2 +- apps/sim/components/icons.tsx | 7 +++++++ 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/apps/sim/app/api/codegen/route.ts b/apps/sim/app/api/codegen/route.ts index 557204c97bb..49aabdaec5e 100644 --- a/apps/sim/app/api/codegen/route.ts +++ b/apps/sim/app/api/codegen/route.ts @@ -25,6 +25,7 @@ type GenerationType = | 'javascript-function-body' | 'typescript-function-body' | 'custom-tool-schema' + | 'json-object' // Define the structure for a single message in the history interface ChatMessage { @@ -281,6 +282,24 @@ if (!response.ok) { const data: unknown = await response.json() // Add type checking/assertion if necessary return data // Ensure you return a value if expected`, + + 'json-object': `You are an expert JSON programmer. +Generate ONLY the raw JSON object based on the user's request. +The output MUST be a single, valid JSON object, starting with { and ending with }. + +Do not include any explanations, markdown formatting, or other text outside the JSON object. + +You have access to the following variables you can use to generate the JSON body: +- 'params' (object): Contains input parameters derived from the JSON schema. Access these directly using the parameter name wrapped in angle brackets, e.g., ''. Do NOT use 'params.paramName'. +- 'environmentVariables' (object): Contains environment variables. Reference these using the double curly brace syntax: '{{ENV_VAR_NAME}}'. Do NOT use 'environmentVariables.VAR_NAME' or env. + +Example: +{ + "name": "", + "age": , + "success": true +} +`, } export async function POST(req: NextRequest) { diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/code.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/code.tsx index b6078b4dee9..f6f4f3ff2a3 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/code.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/code.tsx @@ -1,5 +1,5 @@ import type { ReactElement } from 'react' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { Wand2 } from 'lucide-react' import { highlight, languages } from 'prismjs' import 'prismjs/components/prism-javascript' @@ -24,7 +24,7 @@ interface CodeProps { isConnecting: boolean placeholder?: string language?: 'javascript' | 'json' - generationType?: 'javascript-function-body' | 'json-schema' + generationType?: 'javascript-function-body' | 'json-schema' | 'json-object' value?: string isPreview?: boolean previewValue?: string | null @@ -60,10 +60,16 @@ export function Code({ previewValue, }: CodeProps) { // Determine the AI prompt placeholder based on language - const aiPromptPlaceholder = - language === 'json' - ? 'Describe the JSON schema to generate...' - : 'Describe the JavaScript code to generate...' + const aiPromptPlaceholder = useMemo(() => { + switch (generationType) { + case 'json-schema': + return 'Describe the JSON schema to generate...' + case 'json-object': + return 'Describe the JSON object to generate...' + default: + return 'Describe the JavaScript code to generate...' + } + }, [generationType]) // State management const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) diff --git a/apps/sim/app/w/[id]/hooks/use-code-generation.ts b/apps/sim/app/w/[id]/hooks/use-code-generation.ts index 6eca44d5146..78b7c163f4c 100644 --- a/apps/sim/app/w/[id]/hooks/use-code-generation.ts +++ b/apps/sim/app/w/[id]/hooks/use-code-generation.ts @@ -12,6 +12,7 @@ type GenerationType = | 'javascript-function-body' | 'typescript-function-body' | 'custom-tool-schema' + | 'json-object' interface UseCodeGenerationProps { generationType: GenerationType diff --git a/apps/sim/blocks/blocks/response.ts b/apps/sim/blocks/blocks/response.ts index a65432d33e3..2092290c9d1 100644 --- a/apps/sim/blocks/blocks/response.ts +++ b/apps/sim/blocks/blocks/response.ts @@ -1,4 +1,4 @@ -import { ApiIcon } from '@/components/icons' +import { ResponseIcon } from '@/components/icons' import type { ResponseBlockOutput } from '@/tools/response/types' import type { BlockConfig } from '../types' @@ -11,7 +11,7 @@ export const ResponseBlock: BlockConfig = { docsLink: 'https://docs.simstudio.ai/blocks/response', category: 'blocks', bgColor: '#2F55FF', - icon: ApiIcon, + icon: ResponseIcon, subBlocks: [ { id: 'data', @@ -20,6 +20,7 @@ export const ResponseBlock: BlockConfig = { layout: 'full', placeholder: '{\n "message": "Hello world",\n "userId": ""\n}', language: 'json', + generationType: 'json-object', description: 'Data that will be sent as the response body on API calls. Use to reference workflow variables.', }, diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index b011d38a826..d5bf0f4707c 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -114,7 +114,7 @@ export interface SubBlockConfig { } // Props specific to 'code' sub-block type language?: 'javascript' | 'json' - generationType?: 'javascript-function-body' | 'json-schema' + generationType?: 'javascript-function-body' | 'json-schema' | 'json-object' // OAuth specific properties provider?: string serviceId?: string diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index db6522097fe..c71c808da4b 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2937,3 +2937,10 @@ export function HuggingFaceIcon(props: SVGProps) { ) } + +export const ResponseIcon = (props: SVGProps) => ( + + + + +) From ae5e56e4276322072f021f4848a09d88107255fc Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 18 Jun 2025 18:26:31 +0000 Subject: [PATCH 05/11] chore: add documentation for response block --- apps/docs/components/icons.tsx | 7 + apps/docs/components/ui/block-types.tsx | 17 +- apps/docs/content/docs/blocks/meta.json | 2 +- apps/docs/content/docs/blocks/response.mdx | 235 ++++++++++++++++++ .../docs/public/static/dark/response-dark.png | Bin 0 -> 16325 bytes .../public/static/light/response-light.png | Bin 0 -> 16834 bytes 6 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 apps/docs/content/docs/blocks/response.mdx create mode 100644 apps/docs/public/static/dark/response-dark.png create mode 100644 apps/docs/public/static/light/response-light.png diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index feac645b6ed..876fb6ad018 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -263,3 +263,10 @@ export const SlackIcon = (props: SVGProps) => ( ) + +export const ResponseIcon = (props: SVGProps) => ( + + + + +) diff --git a/apps/docs/components/ui/block-types.tsx b/apps/docs/components/ui/block-types.tsx index 1b2394666e7..96937dc728a 100644 --- a/apps/docs/components/ui/block-types.tsx +++ b/apps/docs/components/ui/block-types.tsx @@ -1,5 +1,13 @@ import { cn } from '@/lib/utils' -import { AgentIcon, ApiIcon, ChartBarIcon, CodeIcon, ConditionalIcon, ConnectIcon } from '../icons' +import { + AgentIcon, + ApiIcon, + ChartBarIcon, + CodeIcon, + ConditionalIcon, + ConnectIcon, + ResponseIcon, +} from '../icons' // Custom Feature component specifically for BlockTypes to handle the 6-item layout const BlockFeature = ({ @@ -127,6 +135,13 @@ export function BlockTypes() { icon: , href: '/blocks/evaluator', }, + { + title: 'Response', + description: + 'Send a response back to the caller with customizable data, status, and headers.', + icon: , + href: '/blocks/response', + }, ] const totalItems = features.length diff --git a/apps/docs/content/docs/blocks/meta.json b/apps/docs/content/docs/blocks/meta.json index b8bfa7fa993..bdefc1c2310 100644 --- a/apps/docs/content/docs/blocks/meta.json +++ b/apps/docs/content/docs/blocks/meta.json @@ -1,4 +1,4 @@ { "title": "Blocks", - "pages": ["agent", "api", "condition", "function", "evaluator", "router", "workflow"] + "pages": ["agent", "api", "condition", "function", "evaluator", "router", "workflow", "response"] } diff --git a/apps/docs/content/docs/blocks/response.mdx b/apps/docs/content/docs/blocks/response.mdx new file mode 100644 index 00000000000..ee2aea01d8a --- /dev/null +++ b/apps/docs/content/docs/blocks/response.mdx @@ -0,0 +1,235 @@ +--- +title: Response +description: Send a structured response back to API calls +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { Step, Steps } from 'fumadocs-ui/components/steps' +import { Tab, Tabs } from 'fumadocs-ui/components/tabs' +import { ThemeImage } from '@/components/ui/theme-image' + +The Response block is the final component in API-enabled workflows that transforms your workflow's variables into a structured HTTP response. This block serves as the endpoint that returns data, status codes, and headers back to API callers. + + + + + Response blocks are terminal blocks - they mark the end of a workflow execution and cannot have further connections. + + +## Overview + +The Response block serves as the final output mechanism for API workflows, enabling you to: + + + + Return structured data: Transform workflow variables into JSON responses + + + Set HTTP status codes: Control the response status (200, 400, 500, etc.) + + + Configure headers: Add custom HTTP headers to the response + + + Reference variables: Use workflow variables dynamically in the response + + + +## Configuration Options + +### Response Data + +The response data is the main content that will be sent back to the API caller. This should be formatted as JSON and can include: + +- Static values +- Dynamic references to workflow variables using the `` syntax +- Nested objects and arrays +- Any valid JSON structure + +### Status Code + +Set the HTTP status code for the response. Common status codes include: + + + +
    +
  • 200: OK - Standard success response
  • +
  • 201: Created - Resource successfully created
  • +
  • 204: No Content - Success with no response body
  • +
+
+ +
    +
  • 400: Bad Request - Invalid request parameters
  • +
  • 401: Unauthorized - Authentication required
  • +
  • 404: Not Found - Resource doesn't exist
  • +
  • 422: Unprocessable Entity - Validation errors
  • +
+
+ +
    +
  • 500: Internal Server Error - Server-side error
  • +
  • 502: Bad Gateway - External service error
  • +
  • 503: Service Unavailable - Service temporarily down
  • +
+
+
+ +

+ Default status code is 200 if not specified. +

+ +### Response Headers + +Configure additional HTTP headers to include in the response. + +Headers are configured as key-value pairs: + +| Key | Value | +|-----|-------| +| Content-Type | application/json | +| Cache-Control | no-cache | +| X-API-Version | 1.0 | + +## Inputs and Outputs + + + +
    +
  • + data (JSON, optional): The JSON data to send in the response body +
  • +
  • + status (number, optional): HTTP status code (default: 200) +
  • +
  • + headers (JSON, optional): Additional response headers +
  • +
+
+ +
    +
  • + response: Complete response object containing: +
      +
    • data: The response body data
    • +
    • status: HTTP status code
    • +
    • headers: Response headers
    • +
    +
  • +
+
+
+ +## Variable References + +Use the `` syntax to dynamically insert workflow variables into your response: + +```json +{ + "user": { + "id": "", + "name": "", + "email": "" + }, + "query": "", + "results": "", + "totalFound": "", + "processingTime": "ms" +} +``` + + + Variable names are case-sensitive and must match exactly with the variables available in your workflow. + + +## Example Usage + +Here's an example of how a Response block might be configured for a user search API: + +```json +{ + "success": true, + "data": { + "users": "", + "pagination": { + "page": "", + "limit": "", + "total": "" + } + }, + "query": { + "searchTerm": "", + "filters": "" + } +} +``` + +With status code: `200` and headers: +| Key | Value | +|-----|-------| +| Content-Type | application/json | +| X-Total-Count | `` | +| Cache-Control | public, max-age=300 | + +## Error Handling Example + +For error responses, you might configure: + +```json +{ + "success": false, + "error": { + "code": "VALIDATION_ERROR", + "message": "", + "details": "" + }, + "timestamp": "" +} +``` + +With status code: `400` + +## Example Usage + +Here's an example of how a Response block might be configured for a user search API: + +```yaml +data: | + { + "success": true, + "data": { + "users": "", + "pagination": { + "page": "", + "limit": "", + "total": "" + } + }, + "query": { + "searchTerm": "", + "filters": "" + } + "timestamp": "" + } +status: 200 +headers: + - key: X-Total-Count + value: + - key: Cache-Control + value: public, max-age=300 +``` + +## Best Practices + +- **Use meaningful status codes**: Choose appropriate HTTP status codes that accurately reflect the outcome of the workflow +- **Structure your responses consistently**: Maintain a consistent JSON structure across all your API endpoints for better developer experience +- **Include relevant metadata**: Add timestamps and version information to help with debugging and monitoring +- **Handle errors gracefully**: Use conditional logic in your workflow to set appropriate error responses with descriptive messages +- **Validate variable references**: Ensure all referenced variables exist and contain the expected data types before the Response block executes \ No newline at end of file diff --git a/apps/docs/public/static/dark/response-dark.png b/apps/docs/public/static/dark/response-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1a12742472677a30bc5e5c67bf23b65cf3bac2af GIT binary patch literal 16325 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 Date: Wed, 18 Jun 2025 19:00:34 +0000 Subject: [PATCH 06/11] chore: rollback temp fix and uncomment original input handler --- apps/sim/executor/index.ts | 101 ++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/apps/sim/executor/index.ts b/apps/sim/executor/index.ts index b6fe6c2d937..211015c51dc 100644 --- a/apps/sim/executor/index.ts +++ b/apps/sim/executor/index.ts @@ -562,8 +562,7 @@ export class Executor { if (starterBlock) { // Initialize the starter block with the workflow input try { - const _blockParams = starterBlock.config.params - /* Commenting out input format handling + const blockParams = starterBlock.config.params const inputFormat = blockParams?.inputFormat // If input format is defined, structure the input according to the schema @@ -577,12 +576,15 @@ export class Executor { // Get the field value from workflow input if available // First try to access via input.field, then directly from field // This handles both input formats: { input: { field: value } } and { field: value } - const inputValue = this.workflowInput?.input?.[field.name] !== undefined - ? this.workflowInput.input[field.name] // Try to get from input.field - : this.workflowInput?.[field.name] // Fallback to direct field access - - logger.info(`[Executor] Processing input field ${field.name} (${field.type}):`, - inputValue !== undefined ? JSON.stringify(inputValue) : 'undefined') + const inputValue = + this.workflowInput?.input?.[field.name] !== undefined + ? this.workflowInput.input[field.name] // Try to get from input.field + : this.workflowInput?.[field.name] // Fallback to direct field access + + logger.info( + `[Executor] Processing input field ${field.name} (${field.type}):`, + inputValue !== undefined ? JSON.stringify(inputValue) : 'undefined' + ) // Convert the value to the appropriate type let typedValue = inputValue @@ -610,15 +612,16 @@ export class Executor { // Check if we managed to process any fields - if not, use the raw input const hasProcessedFields = Object.keys(structuredInput).length > 0 - + // If no fields matched the input format, extract the raw input to use instead - const rawInputData = this.workflowInput?.input !== undefined - ? this.workflowInput.input // Use the nested input data - : this.workflowInput // Fallback to direct input - + const rawInputData = + this.workflowInput?.input !== undefined + ? this.workflowInput.input // Use the nested input data + : this.workflowInput // Fallback to direct input + // Use the structured input if we processed fields, otherwise use raw input const finalInput = hasProcessedFields ? structuredInput : rawInputData - + // Initialize the starter block with structured input // Ensure both input and direct fields are available const starterOutput = { @@ -627,7 +630,7 @@ export class Executor { ...finalInput, // Add input fields directly at response level too }, } - + logger.info(`[Executor] Starter output:`, JSON.stringify(starterOutput, null, 2)) context.blockStates.set(starterBlock.id, { @@ -636,53 +639,47 @@ export class Executor { executionTime: 0, }) } else { - */ - // Handle structured input (like API calls or chat messages) - if (this.workflowInput && typeof this.workflowInput === 'object') { - // Extract the actual input data - if workflowInput has an `input` field, use that - // Otherwise use the entire workflowInput object - const inputData = - this.workflowInput.input !== undefined ? this.workflowInput.input : this.workflowInput + // Handle structured input (like API calls or chat messages) + if (this.workflowInput && typeof this.workflowInput === 'object') { + // Preserve complete workflowInput structure to maintain JSON format + // when referenced through + + const starterOutput = { + response: { + input: this.workflowInput, + // Add top-level fields for backward compatibility + message: this.workflowInput.input, + conversationId: this.workflowInput.conversationId, + }, + } - const starterOutput = { - response: { - input: inputData, - // Add top-level fields for backward compatibility - message: this.workflowInput.input, - conversationId: this.workflowInput.conversationId, - }, - } + context.blockStates.set(starterBlock.id, { + output: starterOutput, + executed: true, + executionTime: 0, + }) + } else { + // Fallback for primitive input values + const starterOutput = { + response: { + input: this.workflowInput, + }, + } - context.blockStates.set(starterBlock.id, { - output: starterOutput, - executed: true, - executionTime: 0, - }) - } else { - // Fallback for primitive input values - const starterOutput = { - response: { - input: this.workflowInput, - }, + context.blockStates.set(starterBlock.id, { + output: starterOutput, + executed: true, + executionTime: 0, + }) } - - context.blockStates.set(starterBlock.id, { - output: starterOutput, - executed: true, - executionTime: 0, - }) } - //} // End of inputFormat conditional } catch (e) { logger.warn('Error processing starter block input format:', e) // Error handler fallback - preserve structure for both direct access and backward compatibility - const inputData = - this.workflowInput?.input !== undefined ? this.workflowInput.input : this.workflowInput - const starterOutput = { response: { - input: inputData, + input: this.workflowInput, message: this.workflowInput?.input, conversationId: this.workflowInput?.conversationId, }, From 69a96eca35917baf5a8e8ebdd7f23340ba23b39b Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 18 Jun 2025 20:19:02 +0000 Subject: [PATCH 07/11] chore: add missing mock for response handler --- .../executor/__test-utils__/executor-mocks.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/apps/sim/executor/__test-utils__/executor-mocks.ts b/apps/sim/executor/__test-utils__/executor-mocks.ts index 43771b070e2..a37187ae56b 100644 --- a/apps/sim/executor/__test-utils__/executor-mocks.ts +++ b/apps/sim/executor/__test-utils__/executor-mocks.ts @@ -39,6 +39,7 @@ export const setupHandlerMocks = () => { ParallelBlockHandler: createMockHandler('parallel'), WorkflowBlockHandler: createMockHandler('workflow'), GenericBlockHandler: createMockHandler('generic'), + ResponseBlockHandler: createMockHandler('response'), })) } @@ -402,6 +403,48 @@ export const createWorkflowWithParallel = (distribution?: any): SerializedWorkfl }, }) +export const createWorkflowWithResponse = (): SerializedWorkflow => ({ + version: '1.0', + blocks: [ + { + id: 'starter', + position: { x: 0, y: 0 }, + config: { tool: 'test-tool', params: {} }, + inputs: { + input: 'json', + }, + outputs: { + response: { + input: 'json', + }, + }, + enabled: true, + metadata: { id: 'starter', name: 'Starter Block' }, + }, + { + id: 'response', + position: { x: 100, y: 0 }, + config: { tool: 'test-tool', params: {} }, + inputs: { + data: 'json', + status: 'number', + headers: 'json', + }, + outputs: { + response: { + data: 'json', + status: 'number', + headers: 'json', + }, + }, + enabled: true, + metadata: { id: 'response', name: 'Response Block' }, + }, + ], + connections: [{ source: 'starter', target: 'response' }], + loops: {}, +}) + /** * Create a mock execution context with customizable options */ From 52578b813554387f86475cfe6fd112a86c94588a Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 18 Jun 2025 20:41:38 +0000 Subject: [PATCH 08/11] chore: add missing mock --- apps/sim/app/api/workflows/[id]/execute/route.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.test.ts b/apps/sim/app/api/workflows/[id]/execute/route.test.ts index e511faa223c..7b673364af3 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.test.ts @@ -109,6 +109,7 @@ describe('Workflow Execution API Route', () => { // Mock workflow run counts vi.doMock('@/lib/workflows/utils', () => ({ updateWorkflowRunCounts: vi.fn().mockResolvedValue(undefined), + workflowHasResponseBlock: vi.fn().mockReturnValue(false), })) // Mock database From 98d7d9c36932459bb57ceef80dec21756475a0d4 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 18 Jun 2025 21:50:50 +0000 Subject: [PATCH 09/11] chore: greptile recommendations --- apps/docs/content/docs/blocks/meta.json | 2 +- apps/docs/content/docs/blocks/response.mdx | 49 +------------------ .../handlers/response/response-handler.ts | 6 ++- 3 files changed, 7 insertions(+), 50 deletions(-) diff --git a/apps/docs/content/docs/blocks/meta.json b/apps/docs/content/docs/blocks/meta.json index bdefc1c2310..98a69a80e8a 100644 --- a/apps/docs/content/docs/blocks/meta.json +++ b/apps/docs/content/docs/blocks/meta.json @@ -1,4 +1,4 @@ { "title": "Blocks", - "pages": ["agent", "api", "condition", "function", "evaluator", "router", "workflow", "response"] + "pages": ["agent", "api", "condition", "function", "evaluator", "router", "response", "workflow"] } diff --git a/apps/docs/content/docs/blocks/response.mdx b/apps/docs/content/docs/blocks/response.mdx index ee2aea01d8a..a53f90a1d91 100644 --- a/apps/docs/content/docs/blocks/response.mdx +++ b/apps/docs/content/docs/blocks/response.mdx @@ -153,53 +153,6 @@ Use the `` syntax to dynamically insert workflow variables into y Here's an example of how a Response block might be configured for a user search API: -```json -{ - "success": true, - "data": { - "users": "", - "pagination": { - "page": "", - "limit": "", - "total": "" - } - }, - "query": { - "searchTerm": "", - "filters": "" - } -} -``` - -With status code: `200` and headers: -| Key | Value | -|-----|-------| -| Content-Type | application/json | -| X-Total-Count | `` | -| Cache-Control | public, max-age=300 | - -## Error Handling Example - -For error responses, you might configure: - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "", - "details": "" - }, - "timestamp": "" -} -``` - -With status code: `400` - -## Example Usage - -Here's an example of how a Response block might be configured for a user search API: - ```yaml data: | { @@ -215,7 +168,7 @@ data: | "query": { "searchTerm": "", "filters": "" - } + }, "timestamp": "" } status: 200 diff --git a/apps/sim/executor/handlers/response/response-handler.ts b/apps/sim/executor/handlers/response/response-handler.ts index 3a0dff90ebb..f96a035aacb 100644 --- a/apps/sim/executor/handlers/response/response-handler.ts +++ b/apps/sim/executor/handlers/response/response-handler.ts @@ -48,7 +48,11 @@ export class ResponseBlockHandler implements BlockHandler { private parseStatus(status?: string): number { if (!status) return 200 - return Number(status) + const parsed = Number(status) + if (Number.isNaN(parsed) || parsed < 100 || parsed > 599) { + return 200 + } + return parsed } private parseHeaders( From 657e0729413745e2900d33bd8ac83d26f6d3aa95 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Fri, 20 Jun 2025 22:41:16 +0000 Subject: [PATCH 10/11] feat: better response builder + doc update --- apps/docs/content/docs/blocks/response.mdx | 4 +- .../docs/public/static/dark/response-dark.png | Bin 16325 -> 25825 bytes .../public/static/light/response-light.png | Bin 16834 -> 25568 bytes .../response/components/property-renderer.tsx | 236 +++++++++++++ .../response/components/value-input.tsx | 300 ++++++++++++++++ .../components/response/response-format.tsx | 326 ++++++++++++++++++ .../components/sub-block/sub-block.tsx | 13 +- apps/sim/blocks/blocks/response.ts | 32 ++ apps/sim/blocks/types.ts | 1 + .../handlers/response/response-handler.ts | 171 ++++++++- 10 files changed, 1079 insertions(+), 4 deletions(-) create mode 100644 apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/components/property-renderer.tsx create mode 100644 apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/components/value-input.tsx create mode 100644 apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/response-format.tsx diff --git a/apps/docs/content/docs/blocks/response.mdx b/apps/docs/content/docs/blocks/response.mdx index a53f90a1d91..2570acd872b 100644 --- a/apps/docs/content/docs/blocks/response.mdx +++ b/apps/docs/content/docs/blocks/response.mdx @@ -14,8 +14,8 @@ The Response block is the final component in API-enabled workflows that transfor lightSrc="/static/light/response-light.png" darkSrc="/static/dark/response-dark.png" alt="Response Block" - width={439} - height={569} + width={430} + height={784} /> diff --git a/apps/docs/public/static/dark/response-dark.png b/apps/docs/public/static/dark/response-dark.png index 1a12742472677a30bc5e5c67bf23b65cf3bac2af..e52919887defb6a2359fd61aab60d728b9f8e02b 100644 GIT binary patch literal 25825 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 We%^PcNfRtf0=c52e*t^mI^=Hw&#Slq literal 16325 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 => void + onAddProperty: (parentId?: string) => void + onRemoveProperty: (id: string) => void + onAddArrayItem: (arrayPropId: string) => void + onRemoveArrayItem: (arrayPropId: string, index: number) => void + onUpdateArrayItem: (arrayPropId: string, index: number, newValue: any) => void + depth?: number +} + +export function PropertyRenderer({ + property, + blockId, + isPreview, + onUpdateProperty, + onAddProperty, + onRemoveProperty, + onAddArrayItem, + onRemoveArrayItem, + onUpdateArrayItem, + depth = 0, +}: PropertyRendererProps) { + const isContainer = property.type === 'object' + const indent = depth * 12 + + // Check if this object is using a variable reference + const isObjectVariable = + property.type === 'object' && + typeof property.value === 'string' && + property.value.trim().startsWith('<') && + property.value.trim().includes('>') + + return ( +
+
+
+ {isContainer && !isObjectVariable && ( + + )} + + + {TYPE_ICONS[property.type]} + + + onUpdateProperty(property.id, { key: e.target.value })} + placeholder='key' + disabled={isPreview} + className='h-6 min-w-0 flex-1 text-xs' + /> + + + + + + + {Object.entries(TYPE_ICONS).map(([type, icon]) => ( + onUpdateProperty(property.id, { type: type as any })} + className='text-xs' + > + {icon} + {type} + + ))} + + + +
+ {isContainer && !isObjectVariable && ( + + )} + + +
+
+ + {/* Show value input for non-container types OR container types using variables */} + {(!isContainer || isObjectVariable) && ( +
+ +
+ )} + + {/* Show object variable input for object types */} + {isContainer && !isObjectVariable && ( +
+ ) => + onUpdateProperty(property.id, updates) + } + onAddArrayItem={onAddArrayItem} + onRemoveArrayItem={onRemoveArrayItem} + onUpdateArrayItem={onUpdateArrayItem} + placeholder='Use or define properties below' + onObjectVariableChange={(newValue: string) => { + if (newValue.startsWith('<')) { + onUpdateProperty(property.id, { value: newValue }) + } else if (newValue === '') { + onUpdateProperty(property.id, { value: [] }) + } + }} + /> +
+ )} +
+ + {isContainer && !property.collapsed && !isObjectVariable && ( +
+ {Array.isArray(property.value) && property.value.length > 0 ? ( + property.value.map((childProp: JSONProperty) => ( + + )) + ) : ( +
+

No properties

+ +
+ )} +
+ )} +
+ ) +} diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/components/value-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/components/value-input.tsx new file mode 100644 index 00000000000..6109affb3aa --- /dev/null +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/components/value-input.tsx @@ -0,0 +1,300 @@ +import { useRef, useState } from 'react' +import { Plus, Trash } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown' +import { Input } from '@/components/ui/input' +import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown' +import { createLogger } from '@/lib/logs/console-logger' +import type { JSONProperty } from '../response-format' + +const logger = createLogger('ValueInput') + +interface ValueInputProps { + property: JSONProperty + blockId: string + isPreview: boolean + onUpdateProperty: (id: string, updates: Partial) => void + onAddArrayItem: (arrayPropId: string) => void + onRemoveArrayItem: (arrayPropId: string, index: number) => void + onUpdateArrayItem: (arrayPropId: string, index: number, newValue: any) => void + placeholder?: string + onObjectVariableChange?: (newValue: string) => void +} + +export function ValueInput({ + property, + blockId, + isPreview, + onUpdateProperty, + onAddArrayItem, + onRemoveArrayItem, + onUpdateArrayItem, + placeholder, + onObjectVariableChange, +}: ValueInputProps) { + const [showEnvVars, setShowEnvVars] = useState(false) + const [showTags, setShowTags] = useState(false) + const [searchTerm, setSearchTerm] = useState('') + const [cursorPosition, setCursorPosition] = useState(0) + const [activeSourceBlockId, setActiveSourceBlockId] = useState(null) + + const inputRefs = useRef<{ [key: string]: HTMLInputElement | null }>({}) + + const findPropertyById = (props: JSONProperty[], id: string): JSONProperty | null => { + for (const prop of props) { + if (prop.id === id) return prop + if (prop.type === 'object' && Array.isArray(prop.value)) { + const found = findPropertyById(prop.value, id) + if (found) return found + } + } + return null + } + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault() + } + + const handleDrop = (e: React.DragEvent, propId: string) => { + if (isPreview) return + e.preventDefault() + + try { + const data = JSON.parse(e.dataTransfer.getData('application/json')) + if (data.type !== 'connectionBlock') return + + const input = inputRefs.current[propId] + const dropPosition = input?.selectionStart ?? 0 + + const currentValue = property.value?.toString() ?? '' + const newValue = `${currentValue.slice(0, dropPosition)}<${currentValue.slice(dropPosition)}` + + input?.focus() + + Promise.resolve().then(() => { + onUpdateProperty(property.id, { value: newValue }) + setCursorPosition(dropPosition + 1) + setShowTags(true) + + if (data.connectionData?.sourceBlockId) { + setActiveSourceBlockId(data.connectionData.sourceBlockId) + } + + setTimeout(() => { + if (input) { + input.selectionStart = dropPosition + 1 + input.selectionEnd = dropPosition + 1 + } + }, 0) + }) + } catch (error) { + logger.error('Failed to parse drop data:', { error }) + } + } + + const getPlaceholder = () => { + if (placeholder) return placeholder + + switch (property.type) { + case 'number': + return '42 or ' + case 'boolean': + return 'true/false or ' + case 'array': + return '["item1", "item2"] or ' + case 'object': + return '{...} or ' + default: + return 'Enter text or ' + } + } + + const handleInputChange = (e: React.ChangeEvent) => { + const newValue = e.target.value + const cursorPos = e.target.selectionStart || 0 + + if (onObjectVariableChange) { + onObjectVariableChange(newValue.trim()) + } else { + onUpdateProperty(property.id, { value: newValue }) + } + + if (!isPreview) { + const tagTrigger = checkTagTrigger(newValue, cursorPos) + const envVarTrigger = checkEnvVarTrigger(newValue, cursorPos) + + setShowTags(tagTrigger.show) + setShowEnvVars(envVarTrigger.show) + setSearchTerm(envVarTrigger.searchTerm || '') + setCursorPosition(cursorPos) + } + } + + const handleTagSelect = (newValue: string) => { + if (onObjectVariableChange) { + onObjectVariableChange(newValue) + } else { + onUpdateProperty(property.id, { value: newValue }) + } + setShowTags(false) + } + + const handleEnvVarSelect = (newValue: string) => { + if (onObjectVariableChange) { + onObjectVariableChange(newValue) + } else { + onUpdateProperty(property.id, { value: newValue }) + } + setShowEnvVars(false) + } + + const isArrayVariable = + property.type === 'array' && + typeof property.value === 'string' && + property.value.trim().startsWith('<') && + property.value.trim().includes('>') + + // Handle array type with individual items + if (property.type === 'array' && !isArrayVariable && Array.isArray(property.value)) { + return ( +
+
+ { + inputRefs.current[`${property.id}-array-variable`] = el + }} + value={typeof property.value === 'string' ? property.value : ''} + onChange={(e) => { + const newValue = e.target.value.trim() + if (newValue.startsWith('<') || newValue.startsWith('[')) { + onUpdateProperty(property.id, { value: newValue }) + } else if (newValue === '') { + onUpdateProperty(property.id, { value: [] }) + } + + const cursorPos = e.target.selectionStart || 0 + if (!isPreview) { + const tagTrigger = checkTagTrigger(newValue, cursorPos) + const envVarTrigger = checkEnvVarTrigger(newValue, cursorPos) + + setShowTags(tagTrigger.show) + setShowEnvVars(envVarTrigger.show) + setSearchTerm(envVarTrigger.searchTerm || '') + setCursorPosition(cursorPos) + } + }} + onDragOver={handleDragOver} + onDrop={(e) => handleDrop(e, `${property.id}-array-variable`)} + placeholder='Use or define items below' + disabled={isPreview} + className='h-7 text-xs' + /> + {!isPreview && showTags && ( + setShowTags(false)} + /> + )} + {!isPreview && showEnvVars && ( + setShowEnvVars(false)} + /> + )} +
+ + {property.value.length > 0 && ( + <> +
Array Items:
+ {property.value.map((item: any, index: number) => ( +
+
+ { + inputRefs.current[`${property.id}-array-${index}`] = el + }} + value={item || ''} + onChange={(e) => onUpdateArrayItem(property.id, index, e.target.value)} + onDragOver={handleDragOver} + onDrop={(e) => handleDrop(e, `${property.id}-array-${index}`)} + placeholder={`Item ${index + 1}`} + disabled={isPreview} + className='h-7 text-xs' + /> +
+ +
+ ))} + + )} + + +
+ ) + } + + // Handle regular input for all other types + return ( +
+ { + inputRefs.current[property.id] = el + }} + value={property.value || ''} + onChange={handleInputChange} + onDragOver={handleDragOver} + onDrop={(e) => handleDrop(e, property.id)} + placeholder={getPlaceholder()} + disabled={isPreview} + className='h-7 text-xs' + /> + {!isPreview && showTags && ( + setShowTags(false)} + /> + )} + {!isPreview && showEnvVars && ( + setShowEnvVars(false)} + /> + )} +
+ ) +} diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/response-format.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/response-format.tsx new file mode 100644 index 00000000000..edef012ccc8 --- /dev/null +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/response/response-format.tsx @@ -0,0 +1,326 @@ +import { useState } from 'react' +import { Code, Eye, Plus } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { Label } from '@/components/ui/label' +import { useSubBlockValue } from '../../hooks/use-sub-block-value' +import { PropertyRenderer } from './components/property-renderer' + +export interface JSONProperty { + id: string + key: string + type: 'string' | 'number' | 'boolean' | 'object' | 'array' + value: any + collapsed?: boolean +} + +interface ResponseFormatProps { + blockId: string + subBlockId: string + isPreview?: boolean + previewValue?: JSONProperty[] | null +} + +const TYPE_ICONS = { + string: 'Aa', + number: '123', + boolean: 'T/F', + object: '{}', + array: '[]', +} + +const TYPE_COLORS = { + string: 'text-green-600 dark:text-green-400', + number: 'text-blue-600 dark:text-blue-400', + boolean: 'text-purple-600 dark:text-purple-400', + object: 'text-orange-600 dark:text-orange-400', + array: 'text-pink-600 dark:text-pink-400', +} + +const DEFAULT_PROPERTY: JSONProperty = { + id: crypto.randomUUID(), + key: 'message', + type: 'string', + value: '', + collapsed: false, +} + +export function ResponseFormat({ + blockId, + subBlockId, + isPreview = false, + previewValue, +}: ResponseFormatProps) { + const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) + const [showPreview, setShowPreview] = useState(false) + + const value = isPreview ? previewValue : storeValue + const properties: JSONProperty[] = value || [DEFAULT_PROPERTY] + + const isVariableReference = (value: any): boolean => { + return typeof value === 'string' && value.trim().startsWith('<') && value.trim().includes('>') + } + + const findPropertyById = (props: JSONProperty[], id: string): JSONProperty | null => { + for (const prop of props) { + if (prop.id === id) return prop + if (prop.type === 'object' && Array.isArray(prop.value)) { + const found = findPropertyById(prop.value, id) + if (found) return found + } + } + return null + } + + const generateJSON = (props: JSONProperty[]): any => { + const result: any = {} + + for (const prop of props) { + if (!prop.key.trim()) return + + let value = prop.value + + if (prop.type === 'object') { + if (Array.isArray(prop.value)) { + value = generateJSON(prop.value) + } else if (typeof prop.value === 'string' && isVariableReference(prop.value)) { + value = prop.value + } else { + value = {} // Default empty object for non-array, non-variable values + } + } else if (prop.type === 'array' && Array.isArray(prop.value)) { + value = prop.value.map((item: any) => { + if (typeof item === 'object' && item.type) { + if (item.type === 'object' && Array.isArray(item.value)) { + return generateJSON(item.value) + } + if (item.type === 'array' && Array.isArray(item.value)) { + return item.value.map((subItem: any) => + typeof subItem === 'object' && subItem.type ? subItem.value : subItem + ) + } + return item.value + } + return item + }) + } else if (prop.type === 'number' && !isVariableReference(value)) { + value = Number.isNaN(Number(value)) ? value : Number(value) + } else if (prop.type === 'boolean' && !isVariableReference(value)) { + const strValue = String(value).toLowerCase().trim() + value = strValue === 'true' || strValue === '1' || strValue === 'yes' || strValue === 'on' + } + + result[prop.key] = value + } + + return result + } + + const updateProperties = (newProperties: JSONProperty[]) => { + if (isPreview) return + setStoreValue(newProperties) + } + + const updateProperty = (id: string, updates: Partial) => { + const updateRecursive = (props: JSONProperty[]): JSONProperty[] => { + return props.map((prop) => { + if (prop.id === id) { + const updated = { ...prop, ...updates } + + if (updates.type && updates.type !== prop.type) { + if (updates.type === 'object') { + updated.value = [] + } else if (updates.type === 'array') { + updated.value = [] + } else if (updates.type === 'boolean') { + updated.value = 'false' + } else if (updates.type === 'number') { + updated.value = '0' + } else { + updated.value = '' + } + } + + return updated + } + + if (prop.type === 'object' && Array.isArray(prop.value)) { + return { ...prop, value: updateRecursive(prop.value) } + } + + return prop + }) + } + + updateProperties(updateRecursive(properties)) + } + + const addProperty = (parentId?: string) => { + const newProp: JSONProperty = { + id: crypto.randomUUID(), + key: '', + type: 'string', + value: '', + collapsed: false, + } + + if (parentId) { + const addToParent = (props: JSONProperty[]): JSONProperty[] => { + return props.map((prop) => { + if (prop.id === parentId && prop.type === 'object') { + return { ...prop, value: [...(prop.value || []), newProp] } + } + if (prop.type === 'object' && Array.isArray(prop.value)) { + return { ...prop, value: addToParent(prop.value) } + } + return prop + }) + } + updateProperties(addToParent(properties)) + } else { + updateProperties([...properties, newProp]) + } + } + + const removeProperty = (id: string) => { + const removeRecursive = (props: JSONProperty[]): JSONProperty[] => { + return props + .filter((prop) => prop.id !== id) + .map((prop) => { + if (prop.type === 'object' && Array.isArray(prop.value)) { + return { ...prop, value: removeRecursive(prop.value) } + } + return prop + }) + } + + const newProperties = removeRecursive(properties) + updateProperties( + newProperties.length > 0 + ? newProperties + : [ + { + id: crypto.randomUUID(), + key: '', + type: 'string', + value: '', + collapsed: false, + }, + ] + ) + } + + const addArrayItem = (arrayPropId: string) => { + const addItem = (props: JSONProperty[]): JSONProperty[] => { + return props.map((prop) => { + if (prop.id === arrayPropId && prop.type === 'array') { + return { ...prop, value: [...(prop.value || []), ''] } + } + if (prop.type === 'object' && Array.isArray(prop.value)) { + return { ...prop, value: addItem(prop.value) } + } + return prop + }) + } + updateProperties(addItem(properties)) + } + + const removeArrayItem = (arrayPropId: string, index: number) => { + const removeItem = (props: JSONProperty[]): JSONProperty[] => { + return props.map((prop) => { + if (prop.id === arrayPropId && prop.type === 'array') { + const newValue = [...(prop.value || [])] + newValue.splice(index, 1) + return { ...prop, value: newValue } + } + if (prop.type === 'object' && Array.isArray(prop.value)) { + return { ...prop, value: removeItem(prop.value) } + } + return prop + }) + } + updateProperties(removeItem(properties)) + } + + const updateArrayItem = (arrayPropId: string, index: number, newValue: any) => { + const updateItem = (props: JSONProperty[]): JSONProperty[] => { + return props.map((prop) => { + if (prop.id === arrayPropId && prop.type === 'array') { + const updatedValue = [...(prop.value || [])] + updatedValue[index] = newValue + return { ...prop, value: updatedValue } + } + if (prop.type === 'object' && Array.isArray(prop.value)) { + return { ...prop, value: updateItem(prop.value) } + } + return prop + }) + } + updateProperties(updateItem(properties)) + } + + const hasConfiguredProperties = properties.some((prop) => prop.key.trim()) + + return ( +
+
+ +
+ + +
+
+ + {showPreview && ( +
+
+            {JSON.stringify(generateJSON(properties), null, 2)}
+          
+
+ )} + +
+ {properties.map((prop) => ( + + ))} +
+ + {!hasConfiguredProperties && ( +
+

Build your JSON response format

+

+ Use <variable.name> in values or drag variables from above +

+
+ )} +
+ ) +} 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 db1fffd61b9..76f6e339b3f 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 @@ -19,6 +19,7 @@ import { FolderSelectorInput } from './components/folder-selector/components/fol import { KnowledgeBaseSelector } from './components/knowledge-base-selector/knowledge-base-selector' import { LongInput } from './components/long-input' import { ProjectSelectorInput } from './components/project-selector/project-selector-input' +import { ResponseFormat } from './components/response/response-format' import { ScheduleConfig } from './components/schedule/schedule-config' import { ShortInput } from './components/short-input' import { SliderInput } from './components/slider-input' @@ -332,7 +333,7 @@ export function SubBlock({ previewValue={previewValue} /> ) - case 'input-format': + case 'input-format': { return ( ) + } + case 'response-format': + return ( + + ) case 'channel-selector': return ( = { bgColor: '#2F55FF', icon: ResponseIcon, subBlocks: [ + { + id: 'dataMode', + title: 'Response Data Mode', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Builder', id: 'structured' }, + { label: 'Editor', id: 'json' }, + ], + value: () => 'structured', + description: 'Choose how to define your response data structure', + }, + { + id: 'builderData', + title: 'Response Structure', + type: 'response-format', + layout: 'full', + condition: { field: 'dataMode', value: 'structured' }, + description: + 'Define the structure of your response data. Use in field names to reference workflow variables.', + }, { id: 'data', title: 'Response Data', @@ -21,6 +42,7 @@ export const ResponseBlock: BlockConfig = { placeholder: '{\n "message": "Hello world",\n "userId": ""\n}', language: 'json', generationType: 'json-object', + condition: { field: 'dataMode', value: 'json' }, description: 'Data that will be sent as the response body on API calls. Use to reference workflow variables.', }, @@ -43,6 +65,16 @@ export const ResponseBlock: BlockConfig = { ], tools: { access: [] }, inputs: { + dataMode: { + type: 'string', + required: false, + description: 'Mode for defining response data structure', + }, + builderData: { + type: 'json', + required: false, + description: 'The JSON data to send in the response body', + }, data: { type: 'json', required: false, diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index d5bf0f4707c..413f5a9fc66 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -34,6 +34,7 @@ export type SubBlockType = | 'knowledge-base-selector' // Knowledge base selector | 'document-selector' // Document selector for knowledge bases | 'input-format' // Input structure format + | 'response-format' // Response structure format | 'file-upload' // File uploader // Component width setting diff --git a/apps/sim/executor/handlers/response/response-handler.ts b/apps/sim/executor/handlers/response/response-handler.ts index f96a035aacb..9450afc0e79 100644 --- a/apps/sim/executor/handlers/response/response-handler.ts +++ b/apps/sim/executor/handlers/response/response-handler.ts @@ -5,6 +5,14 @@ import type { BlockHandler } from '../../types' const logger = createLogger('ResponseBlockHandler') +interface JSONProperty { + id: string + key: string + type: 'string' | 'number' | 'boolean' | 'object' | 'array' + value: any + collapsed?: boolean +} + export class ResponseBlockHandler implements BlockHandler { canHandle(block: SerializedBlock): boolean { return block.metadata?.id === 'response' @@ -14,7 +22,7 @@ export class ResponseBlockHandler implements BlockHandler { logger.info(`Executing response block: ${block.id}`) try { - const responseData = inputs.data || {} + const responseData = this.parseResponseData(inputs) const statusCode = this.parseStatus(inputs.status) const responseHeaders = this.parseHeaders(inputs.headers) @@ -46,6 +54,167 @@ export class ResponseBlockHandler implements BlockHandler { } } + private parseResponseData(inputs: Record): any { + const dataMode = inputs.dataMode || 'structured' + + if (dataMode === 'json' && inputs.data) { + // Handle JSON mode - data comes from code editor + if (typeof inputs.data === 'string') { + try { + return JSON.parse(inputs.data) + } catch (error) { + logger.warn('Failed to parse JSON data, returning as string:', error) + return inputs.data + } + } else if (typeof inputs.data === 'object' && inputs.data !== null) { + // Data is already an object, return as-is + return inputs.data + } + return inputs.data + } + + if (dataMode === 'structured' && inputs.builderData) { + // Handle structured mode - convert builderData to JSON + const convertedData = this.convertBuilderDataToJson(inputs.builderData) + return this.parseObjectStrings(convertedData) + } + + // Fallback to inputs.data for backward compatibility + return inputs.data || {} + } + + private convertBuilderDataToJson(builderData: JSONProperty[]): any { + if (!Array.isArray(builderData)) { + return {} + } + + const result: any = {} + + for (const prop of builderData) { + if (!prop.key.trim()) { + return + } + + const value = this.convertPropertyValue(prop) + result[prop.key] = value + } + + return result + } + + private convertPropertyValue(prop: JSONProperty): any { + switch (prop.type) { + case 'object': + return this.convertObjectValue(prop.value) + case 'array': + return this.convertArrayValue(prop.value) + case 'number': + return this.convertNumberValue(prop.value) + case 'boolean': + return this.convertBooleanValue(prop.value) + default: + return prop.value + } + } + + private convertObjectValue(value: any): any { + if (Array.isArray(value)) { + return this.convertBuilderDataToJson(value) + } + + if (typeof value === 'string' && !this.isVariableReference(value)) { + return this.tryParseJson(value, value) + } + + // Keep variable references or other values as-is (they'll be resolved later) + return value + } + + private convertArrayValue(value: any): any { + if (Array.isArray(value)) { + return value.map((item: any) => this.convertArrayItem(item)) + } + + if (typeof value === 'string' && !this.isVariableReference(value)) { + const parsed = this.tryParseJson(value, value) + return Array.isArray(parsed) ? parsed : value + } + + // Keep variable references or other values as-is + return value + } + + private convertArrayItem(item: any): any { + if (typeof item !== 'object' || !item.type) { + return item + } + + if (item.type === 'object' && Array.isArray(item.value)) { + return this.convertBuilderDataToJson(item.value) + } + + if (item.type === 'array' && Array.isArray(item.value)) { + return item.value.map((subItem: any) => + typeof subItem === 'object' && subItem.type ? subItem.value : subItem + ) + } + + return item.value + } + + private convertNumberValue(value: any): any { + if (this.isVariableReference(value)) { + return value + } + + const numValue = Number(value) + return Number.isNaN(numValue) ? value : numValue + } + + private convertBooleanValue(value: any): any { + if (this.isVariableReference(value)) { + return value + } + + return value === 'true' || value === true + } + + private tryParseJson(jsonString: string, fallback: any): any { + try { + return JSON.parse(jsonString) + } catch { + return fallback + } + } + + private isVariableReference(value: any): boolean { + return typeof value === 'string' && value.trim().startsWith('<') && value.trim().includes('>') + } + + private parseObjectStrings(data: any): any { + if (typeof data === 'string') { + // Try to parse strings that might be JSON objects + try { + const parsed = JSON.parse(data) + if (typeof parsed === 'object' && parsed !== null) { + return this.parseObjectStrings(parsed) // Recursively parse nested objects + } + return parsed + } catch { + return data // Return as string if not valid JSON + } + } else if (Array.isArray(data)) { + return data.map((item) => this.parseObjectStrings(item)) + } else if (typeof data === 'object' && data !== null) { + const result: any = {} + for (const [key, value] of Object.entries(data)) { + result[key] = this.parseObjectStrings(value) + } + return result + } + return data + } + private parseStatus(status?: string): number { if (!status) return 200 const parsed = Number(status) From 143f2064e8aac02e0969cceff4192360e82b4726 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Mon, 23 Jun 2025 11:30:03 +0000 Subject: [PATCH 11/11] fix: allow removing single input field --- .../components/starter/input-format.tsx | 196 ++++++++++-------- 1 file changed, 106 insertions(+), 90 deletions(-) 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 6bac8da852d..6d42ca0aa6a 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 @@ -44,7 +44,7 @@ export function InputFormat({ // Use preview value when in preview mode, otherwise use store value const value = isPreview ? previewValue : storeValue - const fields: InputField[] = value || [DEFAULT_FIELD] + const fields: InputField[] = value || [] // Field operations const addField = () => { @@ -58,7 +58,7 @@ export function InputFormat({ } const removeField = (id: string) => { - if (isPreview || fields.length === 1) return + if (isPreview) return setStoreValue(fields.filter((field: InputField) => field.id !== id)) } @@ -115,7 +115,7 @@ export function InputFormat({ variant='ghost' size='icon' onClick={() => removeField(field.id)} - disabled={isPreview || fields.length === 1} + disabled={isPreview} className='h-6 w-6 rounded-full text-destructive hover:text-destructive' > @@ -132,96 +132,112 @@ export function InputFormat({ // Main render return (
- {fields.map((field, index) => { - const isUnconfigured = !field.name || field.name.trim() === '' - - return ( -
+

No input fields defined

+ - - - updateField(field.id, 'type', 'string')} - className='cursor-pointer' - > - Aa - String - - updateField(field.id, 'type', 'number')} - className='cursor-pointer' - > - 123 - Number - - updateField(field.id, 'type', 'boolean')} - className='cursor-pointer' - > - 0/1 - Boolean - - updateField(field.id, 'type', 'object')} - className='cursor-pointer' - > - {'{}'} - Object - - updateField(field.id, 'type', 'array')} - className='cursor-pointer' - > - [] - Array - - - + + Add Field + +
+ ) : ( + fields.map((field, index) => { + const isUnconfigured = !field.name || field.name.trim() === '' + + return ( +
+ {renderFieldHeader(field, index)} + + {!field.collapsed && ( +
+
+ + updateField(field.id, 'name', e.target.value)} + placeholder='firstName' + disabled={isPreview} + className='h-9 placeholder:text-muted-foreground/50' + /> +
+ +
+ + + + + + + updateField(field.id, 'type', 'string')} + className='cursor-pointer' + > + Aa + String + + updateField(field.id, 'type', 'number')} + className='cursor-pointer' + > + 123 + Number + + updateField(field.id, 'type', 'boolean')} + className='cursor-pointer' + > + 0/1 + Boolean + + updateField(field.id, 'type', 'object')} + className='cursor-pointer' + > + {'{}'} + Object + + updateField(field.id, 'type', 'array')} + className='cursor-pointer' + > + [] + Array + + + +
-
- )} -
- ) - })} + )} + + ) + }) + )} - {!hasConfiguredFields && ( + {fields.length > 0 && !hasConfiguredFields && (
Define fields above to enable structured API input