diff --git a/apps/sim/blocks/blocks/agent.test.ts b/apps/sim/blocks/blocks/agent.test.ts index a0ea12866e..e29ecc5ff3 100644 --- a/apps/sim/blocks/blocks/agent.test.ts +++ b/apps/sim/blocks/blocks/agent.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { AgentBlock } from './agent' +import { AgentBlock } from '@/blocks/blocks/agent' vi.mock('@/blocks', () => ({ getAllBlocks: vi.fn(() => [ diff --git a/apps/sim/blocks/blocks/agent.ts b/apps/sim/blocks/blocks/agent.ts index 776f29944b..bfaaa5a0e0 100644 --- a/apps/sim/blocks/blocks/agent.ts +++ b/apps/sim/blocks/blocks/agent.ts @@ -1,6 +1,7 @@ import { AgentIcon } from '@/components/icons' import { isHosted } from '@/lib/environment' import { createLogger } from '@/lib/logs/console-logger' +import type { BlockConfig } from '@/blocks/types' import { getAllModelProviders, getBaseModelProviders, @@ -13,7 +14,6 @@ import { } from '@/providers/utils' import { useOllamaStore } from '@/stores/ollama/store' import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' const logger = createLogger('AgentBlock') diff --git a/apps/sim/blocks/blocks/airtable.ts b/apps/sim/blocks/blocks/airtable.ts index 676c69d3ea..eb99382499 100644 --- a/apps/sim/blocks/blocks/airtable.ts +++ b/apps/sim/blocks/blocks/airtable.ts @@ -1,19 +1,6 @@ import { AirtableIcon } from '@/components/icons' -import type { - AirtableCreateResponse, - AirtableGetResponse, - AirtableListResponse, - AirtableUpdateMultipleResponse, - AirtableUpdateResponse, -} from '@/tools/airtable/types' -import type { BlockConfig } from '../types' - -type AirtableResponse = - | AirtableListResponse - | AirtableGetResponse - | AirtableCreateResponse - | AirtableUpdateResponse - | AirtableUpdateMultipleResponse +import type { BlockConfig } from '@/blocks/types' +import type { AirtableResponse } from '@/tools/airtable/types' export const AirtableBlock: BlockConfig = { type: 'airtable', diff --git a/apps/sim/blocks/blocks/api.ts b/apps/sim/blocks/blocks/api.ts index 98b1752817..95c7ac171c 100644 --- a/apps/sim/blocks/blocks/api.ts +++ b/apps/sim/blocks/blocks/api.ts @@ -1,6 +1,6 @@ import { ApiIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { RequestResponse } from '@/tools/http/types' -import type { BlockConfig } from '../types' export const ApiBlock: BlockConfig = { type: 'api', diff --git a/apps/sim/blocks/blocks/browser_use.ts b/apps/sim/blocks/blocks/browser_use.ts index 33bc2feabd..adce37ab77 100644 --- a/apps/sim/blocks/blocks/browser_use.ts +++ b/apps/sim/blocks/blocks/browser_use.ts @@ -1,15 +1,6 @@ import { BrowserUseIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' - -interface BrowserUseResponse extends ToolResponse { - output: { - id: string - success: boolean - output: any - steps: any[] - } -} +import type { BlockConfig } from '@/blocks/types' +import type { BrowserUseResponse } from '@/tools/browser_use/types' export const BrowserUseBlock: BlockConfig = { type: 'browser_use', diff --git a/apps/sim/blocks/blocks/clay.ts b/apps/sim/blocks/blocks/clay.ts index 092ce98aaa..9457c45ad7 100644 --- a/apps/sim/blocks/blocks/clay.ts +++ b/apps/sim/blocks/blocks/clay.ts @@ -1,6 +1,6 @@ import { ClayIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { ClayPopulateResponse } from '@/tools/clay/types' -import type { BlockConfig } from '../types' export const ClayBlock: BlockConfig = { type: 'clay', diff --git a/apps/sim/blocks/blocks/condition.ts b/apps/sim/blocks/blocks/condition.ts index 91601094d0..541c7a159c 100644 --- a/apps/sim/blocks/blocks/condition.ts +++ b/apps/sim/blocks/blocks/condition.ts @@ -1,5 +1,5 @@ import { ConditionalIcon } from '@/components/icons' -import type { BlockConfig } from '../types' +import type { BlockConfig } from '@/blocks/types' interface ConditionBlockOutput { success: boolean diff --git a/apps/sim/blocks/blocks/confluence.ts b/apps/sim/blocks/blocks/confluence.ts index c6bfd743cb..2bef786e5b 100644 --- a/apps/sim/blocks/blocks/confluence.ts +++ b/apps/sim/blocks/blocks/confluence.ts @@ -1,8 +1,6 @@ import { ConfluenceIcon } from '@/components/icons' -import type { ConfluenceRetrieveResponse, ConfluenceUpdateResponse } from '@/tools/confluence/types' -import type { BlockConfig } from '../types' - -type ConfluenceResponse = ConfluenceRetrieveResponse | ConfluenceUpdateResponse +import type { BlockConfig } from '@/blocks/types' +import type { ConfluenceResponse } from '@/tools/confluence/types' export const ConfluenceBlock: BlockConfig = { type: 'confluence', @@ -48,7 +46,7 @@ export const ConfluenceBlock: BlockConfig = { ], placeholder: 'Select Confluence account', }, - // Use file-selector component for page selection + // Page selector (basic mode) { id: 'pageId', title: 'Select Page', @@ -57,6 +55,16 @@ export const ConfluenceBlock: BlockConfig = { provider: 'confluence', serviceId: 'confluence', placeholder: 'Select Confluence page', + mode: 'basic', + }, + // Manual page ID input (advanced mode) + { + id: 'manualPageId', + title: 'Page ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Confluence page ID', + mode: 'advanced', }, // Update page fields { @@ -90,10 +98,18 @@ export const ConfluenceBlock: BlockConfig = { } }, params: (params) => { - const { credential, ...rest } = params + const { credential, pageId, manualPageId, ...rest } = params + + // Use the selected page ID or the manually entered one + const effectivePageId = (pageId || manualPageId || '').trim() + + if (!effectivePageId) { + throw new Error('Page ID is required. Please select a page or enter a page ID manually.') + } return { accessToken: credential, + pageId: effectivePageId, ...rest, } }, @@ -103,7 +119,8 @@ export const ConfluenceBlock: BlockConfig = { operation: { type: 'string', required: true }, domain: { type: 'string', required: true }, credential: { type: 'string', required: true }, - pageId: { type: 'string', required: true }, + pageId: { type: 'string', required: false }, + manualPageId: { type: 'string', required: false }, // Update operation inputs title: { type: 'string', required: false }, content: { type: 'string', required: false }, diff --git a/apps/sim/blocks/blocks/discord.ts b/apps/sim/blocks/blocks/discord.ts index 9206c2a6c6..b672c960e9 100644 --- a/apps/sim/blocks/blocks/discord.ts +++ b/apps/sim/blocks/blocks/discord.ts @@ -1,6 +1,6 @@ import { DiscordIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { DiscordResponse } from '@/tools/discord/types' -import type { BlockConfig } from '../types' export const DiscordBlock: BlockConfig = { type: 'discord', @@ -32,6 +32,7 @@ export const DiscordBlock: BlockConfig = { placeholder: 'Enter Discord bot token', password: true, }, + // Server selector (basic mode) { id: 'serverId', title: 'Server', @@ -40,11 +41,26 @@ export const DiscordBlock: BlockConfig = { provider: 'discord', serviceId: 'discord', placeholder: 'Select Discord server', + mode: 'basic', condition: { field: 'operation', value: ['discord_send_message', 'discord_get_messages', 'discord_get_server'], }, }, + // Manual server ID input (advanced mode) + { + id: 'manualServerId', + title: 'Server ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Discord server ID', + mode: 'advanced', + condition: { + field: 'operation', + value: ['discord_send_message', 'discord_get_messages', 'discord_get_server'], + }, + }, + // Channel selector (basic mode) { id: 'channelId', title: 'Channel', @@ -53,6 +69,17 @@ export const DiscordBlock: BlockConfig = { provider: 'discord', serviceId: 'discord', placeholder: 'Select Discord channel', + mode: 'basic', + condition: { field: 'operation', value: ['discord_send_message', 'discord_get_messages'] }, + }, + // Manual channel ID input (advanced mode) + { + id: 'manualChannelId', + title: 'Channel ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Discord channel ID', + mode: 'advanced', condition: { field: 'operation', value: ['discord_send_message', 'discord_get_messages'] }, }, { @@ -108,25 +135,56 @@ export const DiscordBlock: BlockConfig = { if (!params.botToken) throw new Error('Bot token required for this operation') commonParams.botToken = params.botToken + // Handle server ID (selector or manual) + const effectiveServerId = (params.serverId || params.manualServerId || '').trim() + + // Handle channel ID (selector or manual) + const effectiveChannelId = (params.channelId || params.manualChannelId || '').trim() + switch (params.operation) { case 'discord_send_message': + if (!effectiveServerId) { + throw new Error( + 'Server ID is required. Please select a server or enter a server ID manually.' + ) + } + if (!effectiveChannelId) { + throw new Error( + 'Channel ID is required. Please select a channel or enter a channel ID manually.' + ) + } return { ...commonParams, - serverId: params.serverId, - channelId: params.channelId, + serverId: effectiveServerId, + channelId: effectiveChannelId, content: params.content, } case 'discord_get_messages': + if (!effectiveServerId) { + throw new Error( + 'Server ID is required. Please select a server or enter a server ID manually.' + ) + } + if (!effectiveChannelId) { + throw new Error( + 'Channel ID is required. Please select a channel or enter a channel ID manually.' + ) + } return { ...commonParams, - serverId: params.serverId, - channelId: params.channelId, + serverId: effectiveServerId, + channelId: effectiveChannelId, limit: params.limit ? Math.min(Math.max(1, Number(params.limit)), 100) : 10, } case 'discord_get_server': + if (!effectiveServerId) { + throw new Error( + 'Server ID is required. Please select a server or enter a server ID manually.' + ) + } return { ...commonParams, - serverId: params.serverId, + serverId: effectiveServerId, } case 'discord_get_user': return { @@ -143,7 +201,9 @@ export const DiscordBlock: BlockConfig = { operation: { type: 'string', required: true }, botToken: { type: 'string', required: true }, serverId: { type: 'string', required: false }, + manualServerId: { type: 'string', required: false }, channelId: { type: 'string', required: false }, + manualChannelId: { type: 'string', required: false }, content: { type: 'string', required: false }, limit: { type: 'number', required: false }, userId: { type: 'string', required: false }, diff --git a/apps/sim/blocks/blocks/elevenlabs.ts b/apps/sim/blocks/blocks/elevenlabs.ts index 73a2a7cafc..daa386473b 100644 --- a/apps/sim/blocks/blocks/elevenlabs.ts +++ b/apps/sim/blocks/blocks/elevenlabs.ts @@ -1,12 +1,6 @@ import { ElevenLabsIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' - -interface ElevenLabsBlockResponse extends ToolResponse { - output: { - audioUrl: string - } -} +import type { BlockConfig } from '@/blocks/types' +import type { ElevenLabsBlockResponse } from '@/tools/elevenlabs/types' export const ElevenLabsBlock: BlockConfig = { type: 'elevenlabs', diff --git a/apps/sim/blocks/blocks/evaluator.ts b/apps/sim/blocks/blocks/evaluator.ts index 013e6f179f..d2e559f1f8 100644 --- a/apps/sim/blocks/blocks/evaluator.ts +++ b/apps/sim/blocks/blocks/evaluator.ts @@ -1,11 +1,11 @@ import { ChartBarIcon } from '@/components/icons' import { isHosted } from '@/lib/environment' import { createLogger } from '@/lib/logs/console-logger' +import type { BlockConfig, ParamType } from '@/blocks/types' import type { ProviderId } from '@/providers/types' import { getAllModelProviders, getBaseModelProviders, getHostedModels } from '@/providers/utils' import { useOllamaStore } from '@/stores/ollama/store' import type { ToolResponse } from '@/tools/types' -import type { BlockConfig, ParamType } from '../types' const logger = createLogger('EvaluatorBlock') diff --git a/apps/sim/blocks/blocks/exa.ts b/apps/sim/blocks/blocks/exa.ts index 754d3ea2d8..657f27a230 100644 --- a/apps/sim/blocks/blocks/exa.ts +++ b/apps/sim/blocks/blocks/exa.ts @@ -1,17 +1,6 @@ import { ExaAIIcon } from '@/components/icons' -import type { - ExaAnswerResponse, - ExaFindSimilarLinksResponse, - ExaGetContentsResponse, - ExaSearchResponse, -} from '@/tools/exa/types' -import type { BlockConfig } from '../types' - -type ExaResponse = - | ExaSearchResponse - | ExaGetContentsResponse - | ExaFindSimilarLinksResponse - | ExaAnswerResponse +import type { BlockConfig } from '@/blocks/types' +import type { ExaResponse } from '@/tools/exa/types' export const ExaBlock: BlockConfig = { type: 'exa', diff --git a/apps/sim/blocks/blocks/file.ts b/apps/sim/blocks/blocks/file.ts index 460c6f4992..17fe150674 100644 --- a/apps/sim/blocks/blocks/file.ts +++ b/apps/sim/blocks/blocks/file.ts @@ -1,8 +1,8 @@ import { DocumentIcon } from '@/components/icons' import { isProd } from '@/lib/environment' import { createLogger } from '@/lib/logs/console-logger' +import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '@/blocks/types' import type { FileParserOutput } from '@/tools/file/types' -import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '../types' const logger = createLogger('FileBlock') diff --git a/apps/sim/blocks/blocks/firecrawl.ts b/apps/sim/blocks/blocks/firecrawl.ts index 3eb3213cf3..51595b6d7b 100644 --- a/apps/sim/blocks/blocks/firecrawl.ts +++ b/apps/sim/blocks/blocks/firecrawl.ts @@ -1,8 +1,6 @@ import { FirecrawlIcon } from '@/components/icons' -import type { ScrapeResponse, SearchResponse } from '@/tools/firecrawl/types' -import type { BlockConfig } from '../types' - -type FirecrawlResponse = ScrapeResponse | SearchResponse +import type { BlockConfig } from '@/blocks/types' +import type { FirecrawlResponse } from '@/tools/firecrawl/types' export const FirecrawlBlock: BlockConfig = { type: 'firecrawl', diff --git a/apps/sim/blocks/blocks/function.ts b/apps/sim/blocks/blocks/function.ts index f8706924aa..3277eb30e5 100644 --- a/apps/sim/blocks/blocks/function.ts +++ b/apps/sim/blocks/blocks/function.ts @@ -1,6 +1,6 @@ import { CodeIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { CodeExecutionOutput } from '@/tools/function/types' -import type { BlockConfig } from '../types' export const FunctionBlock: BlockConfig = { type: 'function', diff --git a/apps/sim/blocks/blocks/github.ts b/apps/sim/blocks/blocks/github.ts index db57259689..545dbbea33 100644 --- a/apps/sim/blocks/blocks/github.ts +++ b/apps/sim/blocks/blocks/github.ts @@ -1,17 +1,6 @@ import { GithubIcon } from '@/components/icons' -import type { - CreateCommentResponse, - LatestCommitResponse, - PullRequestResponse, - RepoInfoResponse, -} from '@/tools/github/types' -import type { BlockConfig } from '../types' - -type GitHubResponse = - | PullRequestResponse - | CreateCommentResponse - | LatestCommitResponse - | RepoInfoResponse +import type { BlockConfig } from '@/blocks/types' +import type { GitHubResponse } from '@/tools/github/types' export const GitHubBlock: BlockConfig = { type: 'github', diff --git a/apps/sim/blocks/blocks/gmail.ts b/apps/sim/blocks/blocks/gmail.ts index 18ad1d968c..68f0fb2182 100644 --- a/apps/sim/blocks/blocks/gmail.ts +++ b/apps/sim/blocks/blocks/gmail.ts @@ -1,6 +1,6 @@ import { GmailIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { GmailToolResponse } from '@/tools/gmail/types' -import type { BlockConfig } from '../types' export const GmailBlock: BlockConfig = { type: 'gmail', @@ -67,7 +67,7 @@ export const GmailBlock: BlockConfig = { placeholder: 'Email content', condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] }, }, - // Read Email Fields - Add folder selector + // Label/folder selector (basic mode) { id: 'folder', title: 'Label', @@ -80,6 +80,17 @@ export const GmailBlock: BlockConfig = { 'https://www.googleapis.com/auth/gmail.labels', ], placeholder: 'Select Gmail label/folder', + mode: 'basic', + condition: { field: 'operation', value: 'read_gmail' }, + }, + // Manual label/folder input (advanced mode) + { + id: 'manualFolder', + title: 'Label/Folder', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Gmail label name (e.g., INBOX, SENT, or custom label)', + mode: 'advanced', condition: { field: 'operation', value: 'read_gmail' }, }, { @@ -141,11 +152,14 @@ export const GmailBlock: BlockConfig = { }, params: (params) => { // Pass the credential directly from the credential field - const { credential, ...rest } = params + const { credential, folder, manualFolder, ...rest } = params + + // Handle folder input (selector or manual) + const effectiveFolder = (folder || manualFolder || '').trim() // Ensure folder is always provided for read_gmail operation if (rest.operation === 'read_gmail') { - rest.folder = rest.folder || 'INBOX' + rest.folder = effectiveFolder || 'INBOX' } return { @@ -164,6 +178,7 @@ export const GmailBlock: BlockConfig = { body: { type: 'string', required: false }, // Read operation inputs folder: { type: 'string', required: false }, + manualFolder: { type: 'string', required: false }, messageId: { type: 'string', required: false }, unreadOnly: { type: 'boolean', required: false }, // Search operation inputs diff --git a/apps/sim/blocks/blocks/google.ts b/apps/sim/blocks/blocks/google.ts index 4c8b3b18db..b894dac17a 100644 --- a/apps/sim/blocks/blocks/google.ts +++ b/apps/sim/blocks/blocks/google.ts @@ -1,24 +1,6 @@ import { GoogleIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' - -interface GoogleSearchResponse extends ToolResponse { - output: { - items: Array<{ - title: string - link: string - snippet: string - displayLink?: string - pagemap?: Record - }> - searchInformation: { - totalResults: string - searchTime: number - formattedSearchTime: string - formattedTotalResults: string - } - } -} +import type { BlockConfig } from '@/blocks/types' +import type { GoogleSearchResponse } from '@/tools/google/types' export const GoogleSearchBlock: BlockConfig = { type: 'google_search', diff --git a/apps/sim/blocks/blocks/google_calendar.ts b/apps/sim/blocks/blocks/google_calendar.ts index 2b9f13679d..2620651cca 100644 --- a/apps/sim/blocks/blocks/google_calendar.ts +++ b/apps/sim/blocks/blocks/google_calendar.ts @@ -1,19 +1,6 @@ import { GoogleCalendarIcon } from '@/components/icons' -import type { - GoogleCalendarCreateResponse, - GoogleCalendarGetResponse, - GoogleCalendarInviteResponse, - GoogleCalendarListResponse, - GoogleCalendarQuickAddResponse, -} from '@/tools/google_calendar/types' -import type { BlockConfig } from '../types' - -type GoogleCalendarResponse = - | GoogleCalendarCreateResponse - | GoogleCalendarListResponse - | GoogleCalendarGetResponse - | GoogleCalendarQuickAddResponse - | GoogleCalendarInviteResponse +import type { BlockConfig } from '@/blocks/types' +import type { GoogleCalendarResponse } from '@/tools/google_calendar/types' export const GoogleCalendarBlock: BlockConfig = { type: 'google_calendar', @@ -49,6 +36,7 @@ export const GoogleCalendarBlock: BlockConfig = { requiredScopes: ['https://www.googleapis.com/auth/calendar'], placeholder: 'Select Google Calendar account', }, + // Calendar selector (basic mode) { id: 'calendarId', title: 'Calendar', @@ -58,6 +46,16 @@ export const GoogleCalendarBlock: BlockConfig = { serviceId: 'google-calendar', requiredScopes: ['https://www.googleapis.com/auth/calendar'], placeholder: 'Select calendar', + mode: 'basic', + }, + // Manual calendar ID input (advanced mode) + { + id: 'manualCalendarId', + title: 'Calendar ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter calendar ID (e.g., primary or calendar@gmail.com)', + mode: 'advanced', }, // Create Event Fields @@ -220,9 +218,23 @@ export const GoogleCalendarBlock: BlockConfig = { } }, params: (params) => { - const { credential, operation, attendees, replaceExisting, ...rest } = params + const { + credential, + operation, + attendees, + replaceExisting, + calendarId, + manualCalendarId, + ...rest + } = params + + // Handle calendar ID (selector or manual) + const effectiveCalendarId = (calendarId || manualCalendarId || '').trim() - const processedParams = { ...rest } + const processedParams: Record = { + ...rest, + calendarId: effectiveCalendarId || 'primary', + } // Convert comma-separated attendees string to array, only if it has content if (attendees && typeof attendees === 'string' && attendees.trim().length > 0) { @@ -258,6 +270,7 @@ export const GoogleCalendarBlock: BlockConfig = { operation: { type: 'string', required: true }, credential: { type: 'string', required: true }, calendarId: { type: 'string', required: false }, + manualCalendarId: { type: 'string', required: false }, // Create operation inputs summary: { type: 'string', required: false }, diff --git a/apps/sim/blocks/blocks/google_docs.ts b/apps/sim/blocks/blocks/google_docs.ts index 7c012cf2bd..b78280473c 100644 --- a/apps/sim/blocks/blocks/google_docs.ts +++ b/apps/sim/blocks/blocks/google_docs.ts @@ -1,15 +1,6 @@ import { GoogleDocsIcon } from '@/components/icons' -import type { - GoogleDocsCreateResponse, - GoogleDocsReadResponse, - GoogleDocsWriteResponse, -} from '@/tools/google_docs/types' -import type { BlockConfig } from '../types' - -type GoogleDocsResponse = - | GoogleDocsReadResponse - | GoogleDocsWriteResponse - | GoogleDocsCreateResponse +import type { BlockConfig } from '@/blocks/types' +import type { GoogleDocsResponse } from '@/tools/google_docs/types' export const GoogleDocsBlock: BlockConfig = { type: 'google_docs', @@ -45,7 +36,7 @@ export const GoogleDocsBlock: BlockConfig = { requiredScopes: ['https://www.googleapis.com/auth/drive.file'], placeholder: 'Select Google account', }, - // Document Selector for read operation + // Document selector (basic mode) { id: 'documentId', title: 'Select Document', @@ -56,38 +47,18 @@ export const GoogleDocsBlock: BlockConfig = { requiredScopes: [], mimeType: 'application/vnd.google-apps.document', placeholder: 'Select a document', - condition: { field: 'operation', value: 'read' }, + mode: 'basic', + condition: { field: 'operation', value: ['read', 'write'] }, }, - // Document Selector for write operation - { - id: 'documentId', - title: 'Select Document', - type: 'file-selector', - layout: 'full', - provider: 'google-drive', - serviceId: 'google-drive', - requiredScopes: [], - mimeType: 'application/vnd.google-apps.document', - placeholder: 'Select a document', - condition: { field: 'operation', value: 'write' }, - }, - // Manual Document ID for read operation + // Manual document ID input (advanced mode) { id: 'manualDocumentId', - title: 'Or Enter Document ID Manually', + title: 'Document ID', type: 'short-input', layout: 'full', - placeholder: 'ID of the document', - condition: { field: 'operation', value: 'read' }, - }, - // Manual Document ID for write operation - { - id: 'manualDocumentId', - title: 'Or Enter Document ID Manually', - type: 'short-input', - layout: 'full', - placeholder: 'ID of the document', - condition: { field: 'operation', value: 'write' }, + placeholder: 'Enter document ID', + mode: 'advanced', + condition: { field: 'operation', value: ['read', 'write'] }, }, // Create-specific Fields { @@ -98,7 +69,7 @@ export const GoogleDocsBlock: BlockConfig = { placeholder: 'Enter title for the new document', condition: { field: 'operation', value: 'create' }, }, - // Folder Selector for create operation + // Folder selector (basic mode) { id: 'folderSelector', title: 'Select Parent Folder', @@ -109,15 +80,17 @@ export const GoogleDocsBlock: BlockConfig = { requiredScopes: [], mimeType: 'application/vnd.google-apps.folder', placeholder: 'Select a parent folder', + mode: 'basic', condition: { field: 'operation', value: 'create' }, }, - // Manual Folder ID for create operation + // Manual folder ID input (advanced mode) { id: 'folderId', - title: 'Or Enter Parent Folder ID Manually', + title: 'Parent Folder ID', type: 'short-input', layout: 'full', - placeholder: 'ID of the parent folder (leave empty for root folder)', + placeholder: 'Enter parent folder ID (leave empty for root folder)', + mode: 'advanced', condition: { field: 'operation', value: 'create' }, }, // Content Field for write operation diff --git a/apps/sim/blocks/blocks/google_drive.ts b/apps/sim/blocks/blocks/google_drive.ts index 6ca9ccabdc..55cd2ea0b4 100644 --- a/apps/sim/blocks/blocks/google_drive.ts +++ b/apps/sim/blocks/blocks/google_drive.ts @@ -1,15 +1,6 @@ import { GoogleDriveIcon } from '@/components/icons' -import type { - GoogleDriveGetContentResponse, - GoogleDriveListResponse, - GoogleDriveUploadResponse, -} from '@/tools/google_drive/types' -import type { BlockConfig } from '../types' - -type GoogleDriveResponse = - | GoogleDriveUploadResponse - | GoogleDriveGetContentResponse - | GoogleDriveListResponse +import type { BlockConfig } from '@/blocks/types' +import type { GoogleDriveResponse } from '@/tools/google_drive/types' export const GoogleDriveBlock: BlockConfig = { type: 'google_drive', @@ -87,18 +78,17 @@ export const GoogleDriveBlock: BlockConfig = { requiredScopes: ['https://www.googleapis.com/auth/drive.file'], mimeType: 'application/vnd.google-apps.folder', placeholder: 'Select a parent folder', + mode: 'basic', condition: { field: 'operation', value: 'upload' }, }, { - id: 'folderId', - title: 'Or Enter Parent Folder ID Manually', + id: 'manualFolderId', + title: 'Parent Folder ID', type: 'short-input', layout: 'full', - placeholder: 'ID of the parent folder (leave empty for root folder)', - condition: { - field: 'operation', - value: 'upload', - }, + placeholder: 'Enter parent folder ID (leave empty for root folder)', + mode: 'advanced', + condition: { field: 'operation', value: 'upload' }, }, // Get Content Fields // { @@ -160,21 +150,20 @@ export const GoogleDriveBlock: BlockConfig = { requiredScopes: ['https://www.googleapis.com/auth/drive.file'], mimeType: 'application/vnd.google-apps.folder', placeholder: 'Select a parent folder', + mode: 'basic', condition: { field: 'operation', value: 'create_folder' }, }, - // Manual Folder ID input (shown only when no folder is selected) + // Manual Folder ID input (advanced mode) { - id: 'folderId', - title: 'Or Enter Parent Folder ID Manually', + id: 'manualFolderId', + title: 'Parent Folder ID', type: 'short-input', layout: 'full', - placeholder: 'ID of the parent folder (leave empty for root folder)', - condition: { - field: 'operation', - value: 'create_folder', - }, + placeholder: 'Enter parent folder ID (leave empty for root folder)', + mode: 'advanced', + condition: { field: 'operation', value: 'create_folder' }, }, - // List Fields - Folder Selector + // List Fields - Folder Selector (basic mode) { id: 'folderSelector', title: 'Select Folder', @@ -185,19 +174,18 @@ export const GoogleDriveBlock: BlockConfig = { requiredScopes: ['https://www.googleapis.com/auth/drive.file'], mimeType: 'application/vnd.google-apps.folder', placeholder: 'Select a folder to list files from', + mode: 'basic', condition: { field: 'operation', value: 'list' }, }, - // Manual Folder ID input (shown only when no folder is selected) + // Manual Folder ID input (advanced mode) { - id: 'folderId', - title: 'Or Enter Folder ID Manually', + id: 'manualFolderId', + title: 'Folder ID', type: 'short-input', layout: 'full', - placeholder: 'ID of the folder to list (leave empty for root folder)', - condition: { - field: 'operation', - value: 'list', - }, + placeholder: 'Enter folder ID (leave empty for root folder)', + mode: 'advanced', + condition: { field: 'operation', value: 'list' }, }, { id: 'query', @@ -234,14 +222,14 @@ export const GoogleDriveBlock: BlockConfig = { } }, params: (params) => { - const { credential, folderId, folderSelector, mimeType, ...rest } = params + const { credential, folderSelector, manualFolderId, mimeType, ...rest } = params - // Use folderSelector if provided, otherwise use folderId - const effectiveFolderId = folderSelector || folderId || '' + // Use folderSelector if provided, otherwise use manualFolderId + const effectiveFolderId = (folderSelector || manualFolderId || '').trim() return { accessToken: credential, - folderId: effectiveFolderId.trim(), + folderId: effectiveFolderId, pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined, mimeType: mimeType, ...rest, @@ -259,8 +247,8 @@ export const GoogleDriveBlock: BlockConfig = { // Get Content operation inputs // fileId: { type: 'string', required: false }, // List operation inputs - folderId: { type: 'string', required: false }, folderSelector: { type: 'string', required: false }, + manualFolderId: { type: 'string', required: false }, query: { type: 'string', required: false }, pageSize: { type: 'number', required: false }, }, diff --git a/apps/sim/blocks/blocks/google_sheets.ts b/apps/sim/blocks/blocks/google_sheets.ts index 4d8c959d23..13ee4728f9 100644 --- a/apps/sim/blocks/blocks/google_sheets.ts +++ b/apps/sim/blocks/blocks/google_sheets.ts @@ -1,17 +1,6 @@ import { GoogleSheetsIcon } from '@/components/icons' -import type { - GoogleSheetsAppendResponse, - GoogleSheetsReadResponse, - GoogleSheetsUpdateResponse, - GoogleSheetsWriteResponse, -} from '@/tools/google_sheets/types' -import type { BlockConfig } from '../types' - -type GoogleSheetsResponse = - | GoogleSheetsReadResponse - | GoogleSheetsWriteResponse - | GoogleSheetsUpdateResponse - | GoogleSheetsAppendResponse +import type { BlockConfig } from '@/blocks/types' +import type { GoogleSheetsResponse } from '@/tools/google_sheets/types' export const GoogleSheetsBlock: BlockConfig = { type: 'google_sheets', @@ -59,15 +48,16 @@ export const GoogleSheetsBlock: BlockConfig = { requiredScopes: [], mimeType: 'application/vnd.google-apps.spreadsheet', placeholder: 'Select a spreadsheet', + mode: 'basic', }, - // Manual Spreadsheet ID (hidden by default) + // Manual Spreadsheet ID (advanced mode) { id: 'manualSpreadsheetId', - title: 'Or Enter Spreadsheet ID Manually', + title: 'Spreadsheet ID', type: 'short-input', layout: 'full', placeholder: 'ID of the spreadsheet (from URL)', - condition: { field: 'spreadsheetId', value: '' }, + mode: 'advanced', }, // Range { diff --git a/apps/sim/blocks/blocks/huggingface.ts b/apps/sim/blocks/blocks/huggingface.ts index fa88698e5f..73c7df5c14 100644 --- a/apps/sim/blocks/blocks/huggingface.ts +++ b/apps/sim/blocks/blocks/huggingface.ts @@ -1,6 +1,6 @@ import { HuggingFaceIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { HuggingFaceChatResponse } from '@/tools/huggingface/types' -import type { BlockConfig } from '../types' export const HuggingFaceBlock: BlockConfig = { type: 'huggingface', diff --git a/apps/sim/blocks/blocks/image_generator.ts b/apps/sim/blocks/blocks/image_generator.ts index 2f6cffba4f..0a63b7eada 100644 --- a/apps/sim/blocks/blocks/image_generator.ts +++ b/apps/sim/blocks/blocks/image_generator.ts @@ -1,6 +1,6 @@ import { ImageIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { DalleResponse } from '@/tools/openai/types' -import type { BlockConfig } from '../types' export const ImageGeneratorBlock: BlockConfig = { type: 'image_generator', diff --git a/apps/sim/blocks/blocks/jina.ts b/apps/sim/blocks/blocks/jina.ts index 0275dd150b..5724ebd2dd 100644 --- a/apps/sim/blocks/blocks/jina.ts +++ b/apps/sim/blocks/blocks/jina.ts @@ -1,6 +1,6 @@ import { JinaAIIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { ReadUrlResponse } from '@/tools/jina/types' -import type { BlockConfig } from '../types' export const JinaBlock: BlockConfig = { type: 'jina', diff --git a/apps/sim/blocks/blocks/jira.ts b/apps/sim/blocks/blocks/jira.ts index 3ec546d1f6..dffb0f3658 100644 --- a/apps/sim/blocks/blocks/jira.ts +++ b/apps/sim/blocks/blocks/jira.ts @@ -1,17 +1,6 @@ import { JiraIcon } from '@/components/icons' -import type { - JiraRetrieveResponse, - JiraRetrieveResponseBulk, - JiraUpdateResponse, - JiraWriteResponse, -} from '@/tools/jira/types' -import type { BlockConfig } from '../types' - -type JiraResponse = - | JiraRetrieveResponse - | JiraUpdateResponse - | JiraWriteResponse - | JiraRetrieveResponseBulk +import type { BlockConfig } from '@/blocks/types' +import type { JiraResponse } from '@/tools/jira/types' export const JiraBlock: BlockConfig = { type: 'jira', @@ -24,7 +13,6 @@ export const JiraBlock: BlockConfig = { bgColor: '#E0E0E0', icon: JiraIcon, subBlocks: [ - // Operation selector { id: 'operation', title: 'Operation', @@ -62,7 +50,7 @@ export const JiraBlock: BlockConfig = { ], placeholder: 'Select Jira account', }, - // Use file-selector component for issue selection + // Project selector (basic mode) { id: 'projectId', title: 'Select Project', @@ -71,7 +59,18 @@ export const JiraBlock: BlockConfig = { provider: 'jira', serviceId: 'jira', placeholder: 'Select Jira project', + mode: 'basic', + }, + // Manual project ID input (advanced mode) + { + id: 'manualProjectId', + title: 'Project ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Jira project ID', + mode: 'advanced', }, + // Issue selector (basic mode) { id: 'issueKey', title: 'Select Issue', @@ -81,6 +80,17 @@ export const JiraBlock: BlockConfig = { serviceId: 'jira', placeholder: 'Select Jira issue', condition: { field: 'operation', value: ['read', 'update'] }, + mode: 'basic', + }, + // Manual issue key input (advanced mode) + { + id: 'manualIssueKey', + title: 'Issue Key', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Jira issue key', + condition: { field: 'operation', value: ['read', 'update'] }, + mode: 'advanced', }, { id: 'summary', @@ -117,18 +127,32 @@ export const JiraBlock: BlockConfig = { } }, params: (params) => { + const { credential, projectId, manualProjectId, issueKey, manualIssueKey, ...rest } = params + // Base params that are always needed const baseParams = { - accessToken: params.credential, + accessToken: credential, domain: params.domain, } + // Use the selected project ID or the manually entered one + const effectiveProjectId = (projectId || manualProjectId || '').trim() + + // Use the selected issue key or the manually entered one + const effectiveIssueKey = (issueKey || manualIssueKey || '').trim() + // Define allowed parameters for each operation switch (params.operation) { case 'write': { + if (!effectiveProjectId) { + throw new Error( + 'Project ID is required. Please select a project or enter a project ID manually.' + ) + } + // For write operations, only include write-specific fields const writeParams = { - projectId: params.projectId, + projectId: effectiveProjectId, summary: params.summary || '', description: params.description || '', issueType: params.issueType || 'Task', @@ -141,10 +165,21 @@ export const JiraBlock: BlockConfig = { } } case 'update': { + if (!effectiveProjectId) { + throw new Error( + 'Project ID is required. Please select a project or enter a project ID manually.' + ) + } + if (!effectiveIssueKey) { + throw new Error( + 'Issue Key is required. Please select an issue or enter an issue key manually.' + ) + } + // For update operations, only include update-specific fields const updateParams = { - projectId: params.projectId, - issueKey: params.issueKey, + projectId: effectiveProjectId, + issueKey: effectiveIssueKey, summary: params.summary || '', description: params.description || '', } @@ -155,17 +190,29 @@ export const JiraBlock: BlockConfig = { } } case 'read': { + if (!effectiveIssueKey) { + throw new Error( + 'Issue Key is required. Please select an issue or enter an issue key manually.' + ) + } + // For read operations, only include read-specific fields return { ...baseParams, - issueKey: params.issueKey, + issueKey: effectiveIssueKey, } } case 'read-bulk': { + if (!effectiveProjectId) { + throw new Error( + 'Project ID is required. Please select a project or enter a project ID manually.' + ) + } + // For read-bulk operations, only include read-bulk-specific fields return { ...baseParams, - projectId: params.projectId, + projectId: effectiveProjectId, } } default: @@ -178,8 +225,10 @@ export const JiraBlock: BlockConfig = { operation: { type: 'string', required: true }, domain: { type: 'string', required: true }, credential: { type: 'string', required: true }, - issueKey: { type: 'string', required: true }, + issueKey: { type: 'string', required: false }, projectId: { type: 'string', required: false }, + manualProjectId: { type: 'string', required: false }, + manualIssueKey: { type: 'string', required: false }, // Update operation inputs summary: { type: 'string', required: true }, description: { type: 'string', required: false }, diff --git a/apps/sim/blocks/blocks/knowledge.ts b/apps/sim/blocks/blocks/knowledge.ts index 365c85f352..e757a19b6b 100644 --- a/apps/sim/blocks/blocks/knowledge.ts +++ b/apps/sim/blocks/blocks/knowledge.ts @@ -1,5 +1,5 @@ import { PackageSearchIcon } from '@/components/icons' -import type { BlockConfig } from '../types' +import type { BlockConfig } from '@/blocks/types' export const KnowledgeBlock: BlockConfig = { type: 'knowledge', @@ -26,6 +26,25 @@ export const KnowledgeBlock: BlockConfig = { return 'knowledge_search' } }, + params: (params) => { + // Validate required fields for each operation + if (params.operation === 'search' && !params.knowledgeBaseIds) { + throw new Error('Knowledge base IDs are required for search operation') + } + if ( + (params.operation === 'upload_chunk' || params.operation === 'create_document') && + !params.knowledgeBaseId + ) { + throw new Error( + 'Knowledge base ID is required for upload_chunk and create_document operations' + ) + } + if (params.operation === 'upload_chunk' && !params.documentId) { + throw new Error('Document ID is required for upload_chunk operation') + } + + return params + }, }, }, inputs: { diff --git a/apps/sim/blocks/blocks/linear.ts b/apps/sim/blocks/blocks/linear.ts index 8e25458114..8e3c352237 100644 --- a/apps/sim/blocks/blocks/linear.ts +++ b/apps/sim/blocks/blocks/linear.ts @@ -1,8 +1,6 @@ import { LinearIcon } from '@/components/icons' -import type { LinearCreateIssueResponse, LinearReadIssuesResponse } from '@/tools/linear/types' -import type { BlockConfig } from '../types' - -type LinearResponse = LinearReadIssuesResponse | LinearCreateIssueResponse +import type { BlockConfig } from '@/blocks/types' +import type { LinearResponse } from '@/tools/linear/types' export const LinearBlock: BlockConfig = { type: 'linear', @@ -42,6 +40,7 @@ export const LinearBlock: BlockConfig = { provider: 'linear', serviceId: 'linear', placeholder: 'Select a team', + mode: 'basic', }, { id: 'projectId', @@ -51,6 +50,25 @@ export const LinearBlock: BlockConfig = { provider: 'linear', serviceId: 'linear', placeholder: 'Select a project', + mode: 'basic', + }, + // Manual team ID input (advanced mode) + { + id: 'manualTeamId', + title: 'Team ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Linear team ID', + mode: 'advanced', + }, + // Manual project ID input (advanced mode) + { + id: 'manualProjectId', + title: 'Project ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Linear project ID', + mode: 'advanced', }, { id: 'title', @@ -73,19 +91,40 @@ export const LinearBlock: BlockConfig = { tool: (params) => params.operation === 'write' ? 'linear_create_issue' : 'linear_read_issues', params: (params) => { + // Handle team ID (selector or manual) + const effectiveTeamId = (params.teamId || params.manualTeamId || '').trim() + + // Handle project ID (selector or manual) + const effectiveProjectId = (params.projectId || params.manualProjectId || '').trim() + + if (!effectiveTeamId) { + throw new Error('Team ID is required. Please select a team or enter a team ID manually.') + } + if (!effectiveProjectId) { + throw new Error( + 'Project ID is required. Please select a project or enter a project ID manually.' + ) + } + if (params.operation === 'write') { + if (!params.title?.trim()) { + throw new Error('Title is required for creating issues.') + } + if (!params.description?.trim()) { + throw new Error('Description is required for creating issues.') + } return { credential: params.credential, - teamId: params.teamId, - projectId: params.projectId, + teamId: effectiveTeamId, + projectId: effectiveProjectId, title: params.title, description: params.description, } } return { credential: params.credential, - teamId: params.teamId, - projectId: params.projectId, + teamId: effectiveTeamId, + projectId: effectiveProjectId, } }, }, @@ -93,8 +132,10 @@ export const LinearBlock: BlockConfig = { inputs: { operation: { type: 'string', required: true }, credential: { type: 'string', required: true }, - teamId: { type: 'string', required: true }, - projectId: { type: 'string', required: true }, + teamId: { type: 'string', required: false }, + projectId: { type: 'string', required: false }, + manualTeamId: { type: 'string', required: false }, + manualProjectId: { type: 'string', required: false }, title: { type: 'string', required: false }, description: { type: 'string', required: false }, }, diff --git a/apps/sim/blocks/blocks/linkup.ts b/apps/sim/blocks/blocks/linkup.ts index 0cf6f94546..00307bad31 100644 --- a/apps/sim/blocks/blocks/linkup.ts +++ b/apps/sim/blocks/blocks/linkup.ts @@ -1,6 +1,6 @@ import { LinkupIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { LinkupSearchToolResponse } from '@/tools/linkup/types' -import type { BlockConfig } from '../types' export const LinkupBlock: BlockConfig = { type: 'linkup', diff --git a/apps/sim/blocks/blocks/mem0.ts b/apps/sim/blocks/blocks/mem0.ts index e85bf07978..ce138413d5 100644 --- a/apps/sim/blocks/blocks/mem0.ts +++ b/apps/sim/blocks/blocks/mem0.ts @@ -1,6 +1,6 @@ import { Mem0Icon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { Mem0Response } from '@/tools/mem0/types' -import type { BlockConfig } from '../types' export const Mem0Block: BlockConfig = { type: 'mem0', diff --git a/apps/sim/blocks/blocks/memory.ts b/apps/sim/blocks/blocks/memory.ts index 59c3ff9704..a6dd1c98cd 100644 --- a/apps/sim/blocks/blocks/memory.ts +++ b/apps/sim/blocks/blocks/memory.ts @@ -1,5 +1,5 @@ import { BrainIcon } from '@/components/icons' -import type { BlockConfig } from '../types' +import type { BlockConfig } from '@/blocks/types' export const MemoryBlock: BlockConfig = { type: 'memory', diff --git a/apps/sim/blocks/blocks/microsoft_excel.ts b/apps/sim/blocks/blocks/microsoft_excel.ts index 9cd294d366..cd37c5423f 100644 --- a/apps/sim/blocks/blocks/microsoft_excel.ts +++ b/apps/sim/blocks/blocks/microsoft_excel.ts @@ -1,15 +1,6 @@ import { MicrosoftExcelIcon } from '@/components/icons' -import type { - MicrosoftExcelReadResponse, - MicrosoftExcelTableAddResponse, - MicrosoftExcelWriteResponse, -} from '@/tools/microsoft_excel/types' -import type { BlockConfig } from '../types' - -type MicrosoftExcelResponse = - | MicrosoftExcelReadResponse - | MicrosoftExcelWriteResponse - | MicrosoftExcelTableAddResponse +import type { BlockConfig } from '@/blocks/types' +import type { MicrosoftExcelResponse } from '@/tools/microsoft_excel/types' export const MicrosoftExcelBlock: BlockConfig = { type: 'microsoft_excel', @@ -53,14 +44,15 @@ export const MicrosoftExcelBlock: BlockConfig = { requiredScopes: [], mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', placeholder: 'Select a spreadsheet', + mode: 'basic', }, { id: 'manualSpreadsheetId', - title: 'Or Enter Spreadsheet ID Manually', + title: 'Spreadsheet ID', type: 'short-input', layout: 'full', - placeholder: 'ID of the spreadsheet (from URL)', - condition: { field: 'spreadsheetId', value: '' }, + placeholder: 'Enter spreadsheet ID', + mode: 'advanced', }, { id: 'range', diff --git a/apps/sim/blocks/blocks/microsoft_teams.ts b/apps/sim/blocks/blocks/microsoft_teams.ts index 9296e75496..b140cbe077 100644 --- a/apps/sim/blocks/blocks/microsoft_teams.ts +++ b/apps/sim/blocks/blocks/microsoft_teams.ts @@ -1,11 +1,6 @@ import { MicrosoftTeamsIcon } from '@/components/icons' -import type { - MicrosoftTeamsReadResponse, - MicrosoftTeamsWriteResponse, -} from '@/tools/microsoft_teams/types' -import type { BlockConfig } from '../types' - -type MicrosoftTeamsResponse = MicrosoftTeamsReadResponse | MicrosoftTeamsWriteResponse +import type { BlockConfig } from '@/blocks/types' +import type { MicrosoftTeamsResponse } from '@/tools/microsoft_teams/types' export const MicrosoftTeamsBlock: BlockConfig = { type: 'microsoft_teams', @@ -64,6 +59,16 @@ export const MicrosoftTeamsBlock: BlockConfig = { serviceId: 'microsoft-teams', requiredScopes: [], placeholder: 'Select a team', + mode: 'basic', + condition: { field: 'operation', value: ['read_channel', 'write_channel'] }, + }, + { + id: 'manualTeamId', + title: 'Team ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter team ID', + mode: 'advanced', condition: { field: 'operation', value: ['read_channel', 'write_channel'] }, }, { @@ -75,6 +80,16 @@ export const MicrosoftTeamsBlock: BlockConfig = { serviceId: 'microsoft-teams', requiredScopes: [], placeholder: 'Select a chat', + mode: 'basic', + condition: { field: 'operation', value: ['read_chat', 'write_chat'] }, + }, + { + id: 'manualChatId', + title: 'Chat ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter chat ID', + mode: 'advanced', condition: { field: 'operation', value: ['read_chat', 'write_chat'] }, }, { @@ -86,6 +101,16 @@ export const MicrosoftTeamsBlock: BlockConfig = { serviceId: 'microsoft-teams', requiredScopes: [], placeholder: 'Select a channel', + mode: 'basic', + condition: { field: 'operation', value: ['read_channel', 'write_channel'] }, + }, + { + id: 'manualChannelId', + title: 'Channel ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter channel ID', + mode: 'advanced', condition: { field: 'operation', value: ['read_channel', 'write_channel'] }, }, // Create-specific Fields @@ -121,7 +146,22 @@ export const MicrosoftTeamsBlock: BlockConfig = { } }, params: (params) => { - const { credential, operation, ...rest } = params + const { + credential, + operation, + teamId, + manualTeamId, + chatId, + manualChatId, + channelId, + manualChannelId, + ...rest + } = params + + // Use the selected IDs or the manually entered ones + const effectiveTeamId = (teamId || manualTeamId || '').trim() + const effectiveChatId = (chatId || manualChatId || '').trim() + const effectiveChannelId = (channelId || manualChannelId || '').trim() // Build the parameters based on operation type const baseParams = { @@ -131,27 +171,33 @@ export const MicrosoftTeamsBlock: BlockConfig = { // For chat operations, we need chatId if (operation === 'read_chat' || operation === 'write_chat') { - if (!params.chatId) { - throw new Error('Chat ID is required for chat operations') + if (!effectiveChatId) { + throw new Error( + 'Chat ID is required for chat operations. Please select a chat or enter a chat ID manually.' + ) } return { ...baseParams, - chatId: params.chatId, + chatId: effectiveChatId, } } // For channel operations, we need teamId and channelId if (operation === 'read_channel' || operation === 'write_channel') { - if (!params.teamId) { - throw new Error('Team ID is required for channel operations') + if (!effectiveTeamId) { + throw new Error( + 'Team ID is required for channel operations. Please select a team or enter a team ID manually.' + ) } - if (!params.channelId) { - throw new Error('Channel ID is required for channel operations') + if (!effectiveChannelId) { + throw new Error( + 'Channel ID is required for channel operations. Please select a channel or enter a channel ID manually.' + ) } return { ...baseParams, - teamId: params.teamId, - channelId: params.channelId, + teamId: effectiveTeamId, + channelId: effectiveChannelId, } } @@ -162,11 +208,14 @@ export const MicrosoftTeamsBlock: BlockConfig = { inputs: { operation: { type: 'string', required: true }, credential: { type: 'string', required: true }, - messageId: { type: 'string', required: true }, - chatId: { type: 'string', required: true }, - channelId: { type: 'string', required: true }, - teamId: { type: 'string', required: true }, - content: { type: 'string', required: true }, + messageId: { type: 'string', required: false }, + chatId: { type: 'string', required: false }, + manualChatId: { type: 'string', required: false }, + channelId: { type: 'string', required: false }, + manualChannelId: { type: 'string', required: false }, + teamId: { type: 'string', required: false }, + manualTeamId: { type: 'string', required: false }, + content: { type: 'string', required: false }, }, outputs: { content: 'string', diff --git a/apps/sim/blocks/blocks/mistral_parse.ts b/apps/sim/blocks/blocks/mistral_parse.ts index f89f372e0c..1519d7aa76 100644 --- a/apps/sim/blocks/blocks/mistral_parse.ts +++ b/apps/sim/blocks/blocks/mistral_parse.ts @@ -1,7 +1,7 @@ import { MistralIcon } from '@/components/icons' import { isProd } from '@/lib/environment' +import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '@/blocks/types' import type { MistralParserOutput } from '@/tools/mistral/types' -import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '../types' const shouldEnableFileUpload = isProd diff --git a/apps/sim/blocks/blocks/notion.ts b/apps/sim/blocks/blocks/notion.ts index 10447873f4..af9a31a3c2 100644 --- a/apps/sim/blocks/blocks/notion.ts +++ b/apps/sim/blocks/blocks/notion.ts @@ -1,6 +1,6 @@ import { NotionIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { NotionResponse } from '@/tools/notion/types' -import type { BlockConfig } from '../types' export const NotionBlock: BlockConfig = { type: 'notion', diff --git a/apps/sim/blocks/blocks/openai.ts b/apps/sim/blocks/blocks/openai.ts index 7a67bd9c1a..2f5cffbd81 100644 --- a/apps/sim/blocks/blocks/openai.ts +++ b/apps/sim/blocks/blocks/openai.ts @@ -1,5 +1,5 @@ import { OpenAIIcon } from '@/components/icons' -import type { BlockConfig } from '../types' +import type { BlockConfig } from '@/blocks/types' export const OpenAIBlock: BlockConfig = { type: 'openai', diff --git a/apps/sim/blocks/blocks/outlook.ts b/apps/sim/blocks/blocks/outlook.ts index 67d0b57323..0c5a97a39a 100644 --- a/apps/sim/blocks/blocks/outlook.ts +++ b/apps/sim/blocks/blocks/outlook.ts @@ -1,14 +1,8 @@ import { OutlookIcon } from '@/components/icons' -import type { - OutlookDraftResponse, - OutlookReadResponse, - OutlookSendResponse, -} from '@/tools/outlook/types' -import type { BlockConfig } from '../types' +import type { BlockConfig } from '@/blocks/types' +import type { OutlookResponse } from '@/tools/outlook/types' -export const OutlookBlock: BlockConfig< - OutlookReadResponse | OutlookSendResponse | OutlookDraftResponse -> = { +export const OutlookBlock: BlockConfig = { type: 'outlook', name: 'Outlook', description: 'Access Outlook', @@ -19,7 +13,6 @@ export const OutlookBlock: BlockConfig< bgColor: '#E0E0E0', icon: OutlookIcon, subBlocks: [ - // Operation selector { id: 'operation', title: 'Operation', @@ -31,7 +24,6 @@ export const OutlookBlock: BlockConfig< { label: 'Read Email', id: 'read_outlook' }, ], }, - // Gmail Credentials { id: 'credential', title: 'Microsoft Account', @@ -51,7 +43,6 @@ export const OutlookBlock: BlockConfig< ], placeholder: 'Select Microsoft account', }, - // Send Email Fields { id: 'to', title: 'To', @@ -76,7 +67,7 @@ export const OutlookBlock: BlockConfig< placeholder: 'Email content', condition: { field: 'operation', value: ['send_outlook', 'draft_outlook'] }, }, - // Read Email Fields - Add folder selector + // Read Email Fields - Add folder selector (basic mode) { id: 'folder', title: 'Folder', @@ -86,6 +77,17 @@ export const OutlookBlock: BlockConfig< serviceId: 'outlook', requiredScopes: ['Mail.ReadWrite', 'Mail.ReadBasic', 'Mail.Read'], placeholder: 'Select Outlook folder', + mode: 'basic', + condition: { field: 'operation', value: 'read_outlook' }, + }, + // Manual folder input (advanced mode) + { + id: 'manualFolder', + title: 'Folder', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Outlook folder name (e.g., INBOX, SENT, or custom folder)', + mode: 'advanced', condition: { field: 'operation', value: 'read_outlook' }, }, { @@ -114,11 +116,14 @@ export const OutlookBlock: BlockConfig< }, params: (params) => { // Pass the credential directly from the credential field - const { credential, ...rest } = params + const { credential, folder, manualFolder, ...rest } = params + + // Handle folder input (selector or manual) + const effectiveFolder = (folder || manualFolder || '').trim() // Set default folder to INBOX if not specified - if (rest.operation === 'read_outlook' && !rest.folder) { - rest.folder = 'INBOX' + if (rest.operation === 'read_outlook') { + rest.folder = effectiveFolder || 'INBOX' } return { @@ -137,6 +142,7 @@ export const OutlookBlock: BlockConfig< body: { type: 'string', required: false }, // Read operation inputs folder: { type: 'string', required: false }, + manualFolder: { type: 'string', required: false }, maxResults: { type: 'number', required: false }, }, outputs: { diff --git a/apps/sim/blocks/blocks/perplexity.ts b/apps/sim/blocks/blocks/perplexity.ts index b36856a5ed..53c09a373e 100644 --- a/apps/sim/blocks/blocks/perplexity.ts +++ b/apps/sim/blocks/blocks/perplexity.ts @@ -1,18 +1,6 @@ import { PerplexityIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' - -interface PerplexityChatResponse extends ToolResponse { - output: { - content: string - model: string - usage: { - prompt_tokens: number - completion_tokens: number - total_tokens: number - } - } -} +import type { BlockConfig } from '@/blocks/types' +import type { PerplexityChatResponse } from '@/tools/perplexity/types' export const PerplexityBlock: BlockConfig = { type: 'perplexity', diff --git a/apps/sim/blocks/blocks/pinecone.ts b/apps/sim/blocks/blocks/pinecone.ts index fea14bf697..2bbca16bba 100644 --- a/apps/sim/blocks/blocks/pinecone.ts +++ b/apps/sim/blocks/blocks/pinecone.ts @@ -1,7 +1,6 @@ import { PineconeIcon } from '@/components/icons' -// You'll need to create this icon +import type { BlockConfig } from '@/blocks/types' import type { PineconeResponse } from '@/tools/pinecone/types' -import type { BlockConfig } from '../types' export const PineconeBlock: BlockConfig = { type: 'pinecone', diff --git a/apps/sim/blocks/blocks/reddit.ts b/apps/sim/blocks/blocks/reddit.ts index eb129f4e1b..f5e404b927 100644 --- a/apps/sim/blocks/blocks/reddit.ts +++ b/apps/sim/blocks/blocks/reddit.ts @@ -1,14 +1,8 @@ import { RedditIcon } from '@/components/icons' -import type { - RedditCommentsResponse, - RedditHotPostsResponse, - RedditPostsResponse, -} from '@/tools/reddit/types' -import type { BlockConfig } from '../types' +import type { BlockConfig } from '@/blocks/types' +import type { RedditResponse } from '@/tools/reddit/types' -export const RedditBlock: BlockConfig< - RedditHotPostsResponse | RedditPostsResponse | RedditCommentsResponse -> = { +export const RedditBlock: BlockConfig = { type: 'reddit', name: 'Reddit', description: 'Access Reddit data and content', diff --git a/apps/sim/blocks/blocks/response.ts b/apps/sim/blocks/blocks/response.ts index 3e6ba92d8d..6dd5581382 100644 --- a/apps/sim/blocks/blocks/response.ts +++ b/apps/sim/blocks/blocks/response.ts @@ -1,6 +1,6 @@ import { ResponseIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { ResponseBlockOutput } from '@/tools/response/types' -import type { BlockConfig } from '../types' export const ResponseBlock: BlockConfig = { type: 'response', diff --git a/apps/sim/blocks/blocks/router.ts b/apps/sim/blocks/blocks/router.ts index c986cb3b43..4f48f5483a 100644 --- a/apps/sim/blocks/blocks/router.ts +++ b/apps/sim/blocks/blocks/router.ts @@ -1,10 +1,10 @@ import { ConnectIcon } from '@/components/icons' import { isHosted } from '@/lib/environment' +import type { BlockConfig } from '@/blocks/types' import type { ProviderId } from '@/providers/types' import { getAllModelProviders, getBaseModelProviders, getHostedModels } from '@/providers/utils' import { useOllamaStore } from '@/stores/ollama/store' import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' interface RouterResponse extends ToolResponse { output: { diff --git a/apps/sim/blocks/blocks/s3.ts b/apps/sim/blocks/blocks/s3.ts index a5d4e86bdc..99525f4282 100644 --- a/apps/sim/blocks/blocks/s3.ts +++ b/apps/sim/blocks/blocks/s3.ts @@ -1,6 +1,6 @@ import { S3Icon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { S3Response } from '@/tools/s3/types' -import type { BlockConfig } from '../types' export const S3Block: BlockConfig = { type: 's3', diff --git a/apps/sim/blocks/blocks/serper.ts b/apps/sim/blocks/blocks/serper.ts index 731724cdc0..6e1b899376 100644 --- a/apps/sim/blocks/blocks/serper.ts +++ b/apps/sim/blocks/blocks/serper.ts @@ -1,6 +1,6 @@ import { SerperIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { SearchResponse } from '@/tools/serper/types' -import type { BlockConfig } from '../types' export const SerperBlock: BlockConfig = { type: 'serper', diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index c8a58182c2..b839462ff9 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -1,8 +1,6 @@ import { SlackIcon } from '@/components/icons' -import type { SlackMessageResponse } from '@/tools/slack/types' -import type { BlockConfig } from '../types' - -type SlackResponse = SlackMessageResponse +import type { BlockConfig } from '@/blocks/types' +import type { SlackResponse } from '@/tools/slack/types' export const SlackBlock: BlockConfig = { type: 'slack', @@ -76,6 +74,16 @@ export const SlackBlock: BlockConfig = { layout: 'full', provider: 'slack', placeholder: 'Select Slack channel', + mode: 'basic', + }, + // Manual channel ID input (advanced mode) + { + id: 'manualChannel', + title: 'Channel ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Slack channel ID (e.g., C1234567890)', + mode: 'advanced', }, { id: 'text', @@ -97,10 +105,21 @@ export const SlackBlock: BlockConfig = { } }, params: (params) => { - const { credential, authMethod, botToken, operation, ...rest } = params + const { credential, authMethod, botToken, operation, channel, manualChannel, ...rest } = + params + + // Handle channel input (selector or manual) + const effectiveChannel = (channel || manualChannel || '').trim() + + if (!effectiveChannel) { + throw new Error( + 'Channel is required. Please select a channel or enter a channel ID manually.' + ) + } - const baseParams = { + const baseParams: Record = { ...rest, + channel: effectiveChannel, } // Handle authentication based on method @@ -126,7 +145,8 @@ export const SlackBlock: BlockConfig = { authMethod: { type: 'string', required: true }, credential: { type: 'string', required: false }, botToken: { type: 'string', required: false }, - channel: { type: 'string', required: true }, + channel: { type: 'string', required: false }, + manualChannel: { type: 'string', required: false }, text: { type: 'string', required: true }, }, outputs: { diff --git a/apps/sim/blocks/blocks/stagehand.ts b/apps/sim/blocks/blocks/stagehand.ts index bd66fe4e92..5ca07a9f31 100644 --- a/apps/sim/blocks/blocks/stagehand.ts +++ b/apps/sim/blocks/blocks/stagehand.ts @@ -1,8 +1,8 @@ import { StagehandIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' -interface StagehandExtractResponse extends ToolResponse { +export interface StagehandExtractResponse extends ToolResponse { output: { data: Record } diff --git a/apps/sim/blocks/blocks/stagehand_agent.ts b/apps/sim/blocks/blocks/stagehand_agent.ts index bd8ddd368c..f55d3a5051 100644 --- a/apps/sim/blocks/blocks/stagehand_agent.ts +++ b/apps/sim/blocks/blocks/stagehand_agent.ts @@ -1,22 +1,6 @@ import { StagehandIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' - -interface StagehandAgentResponse extends ToolResponse { - output: { - agentResult: { - success: boolean - completed: boolean - message: string - actions: Array<{ - type: string - params: Record - result: Record - }> - } - structuredOutput?: Record - } -} +import type { BlockConfig } from '@/blocks/types' +import type { StagehandAgentResponse } from '@/tools/stagehand/types' export const StagehandAgentBlock: BlockConfig = { type: 'stagehand_agent', diff --git a/apps/sim/blocks/blocks/starter.ts b/apps/sim/blocks/blocks/starter.ts index 1777366898..0af83aefee 100644 --- a/apps/sim/blocks/blocks/starter.ts +++ b/apps/sim/blocks/blocks/starter.ts @@ -1,5 +1,5 @@ import { StartIcon } from '@/components/icons' -import type { BlockConfig } from '../types' +import type { BlockConfig } from '@/blocks/types' export const StarterBlock: BlockConfig = { type: 'starter', diff --git a/apps/sim/blocks/blocks/supabase.ts b/apps/sim/blocks/blocks/supabase.ts index 2baa32c62e..cf21a74568 100644 --- a/apps/sim/blocks/blocks/supabase.ts +++ b/apps/sim/blocks/blocks/supabase.ts @@ -1,14 +1,6 @@ import { SupabaseIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' - -interface SupabaseResponse extends ToolResponse { - output: { - message: string - results: any - } - error?: string -} +import type { BlockConfig } from '@/blocks/types' +import type { SupabaseResponse } from '@/tools/supabase/types' export const SupabaseBlock: BlockConfig = { type: 'supabase', @@ -21,7 +13,6 @@ export const SupabaseBlock: BlockConfig = { bgColor: '#1C1C1C', icon: SupabaseIcon, subBlocks: [ - // Operation selector { id: 'operation', title: 'Operation', @@ -32,7 +23,6 @@ export const SupabaseBlock: BlockConfig = { { label: 'Insert Rows', id: 'insert' }, ], }, - // Common Fields { id: 'projectId', title: 'Project ID', @@ -55,7 +45,6 @@ export const SupabaseBlock: BlockConfig = { placeholder: 'Your Supabase client anon key', password: true, }, - // Insert-specific Fields { id: 'data', title: 'Data', diff --git a/apps/sim/blocks/blocks/tavily.ts b/apps/sim/blocks/blocks/tavily.ts index 1192989976..0a1664fbf9 100644 --- a/apps/sim/blocks/blocks/tavily.ts +++ b/apps/sim/blocks/blocks/tavily.ts @@ -1,8 +1,6 @@ import { TavilyIcon } from '@/components/icons' -import type { TavilyExtractResponse, TavilySearchResponse } from '@/tools/tavily/types' -import type { BlockConfig } from '../types' - -type TavilyResponse = TavilySearchResponse | TavilyExtractResponse +import type { BlockConfig } from '@/blocks/types' +import type { TavilyResponse } from '@/tools/tavily/types' export const TavilyBlock: BlockConfig = { type: 'tavily', @@ -15,7 +13,6 @@ export const TavilyBlock: BlockConfig = { bgColor: '#0066FF', icon: TavilyIcon, subBlocks: [ - // Operation selector { id: 'operation', title: 'Operation', @@ -27,7 +24,6 @@ export const TavilyBlock: BlockConfig = { ], value: () => 'tavily_search', }, - // API Key (common) { id: 'apiKey', title: 'API Key', @@ -36,7 +32,6 @@ export const TavilyBlock: BlockConfig = { placeholder: 'Enter your Tavily API key', password: true, }, - // Search operation inputs { id: 'query', title: 'Search Query', @@ -53,7 +48,6 @@ export const TavilyBlock: BlockConfig = { placeholder: '5', condition: { field: 'operation', value: 'tavily_search' }, }, - // Extract operation inputs { id: 'urls', title: 'URL', @@ -93,10 +87,8 @@ export const TavilyBlock: BlockConfig = { inputs: { operation: { type: 'string', required: true }, apiKey: { type: 'string', required: true }, - // Search operation query: { type: 'string', required: false }, maxResults: { type: 'number', required: false }, - // Extract operation urls: { type: 'string', required: false }, extract_depth: { type: 'string', required: false }, }, diff --git a/apps/sim/blocks/blocks/telegram.ts b/apps/sim/blocks/blocks/telegram.ts index 0e1180abfa..7599099a95 100644 --- a/apps/sim/blocks/blocks/telegram.ts +++ b/apps/sim/blocks/blocks/telegram.ts @@ -1,6 +1,6 @@ import { TelegramIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { TelegramMessageResponse } from '@/tools/telegram/types' -import type { BlockConfig } from '../types' export const TelegramBlock: BlockConfig = { type: 'telegram', diff --git a/apps/sim/blocks/blocks/thinking.ts b/apps/sim/blocks/blocks/thinking.ts index 63231c96ff..4fdd7d6c17 100644 --- a/apps/sim/blocks/blocks/thinking.ts +++ b/apps/sim/blocks/blocks/thinking.ts @@ -1,12 +1,6 @@ import { BrainIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' - -interface ThinkingToolResponse extends ToolResponse { - output: { - acknowledgedThought: string - } -} +import type { BlockConfig } from '@/blocks/types' +import type { ThinkingToolResponse } from '@/tools/thinking/types' export const ThinkingBlock: BlockConfig = { type: 'thinking', diff --git a/apps/sim/blocks/blocks/translate.ts b/apps/sim/blocks/blocks/translate.ts index 6d083a7a34..160a79165d 100644 --- a/apps/sim/blocks/blocks/translate.ts +++ b/apps/sim/blocks/blocks/translate.ts @@ -1,7 +1,7 @@ import { TranslateIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { ProviderId } from '@/providers/types' import { getBaseModelProviders } from '@/providers/utils' -import type { BlockConfig } from '../types' const getTranslationPrompt = ( targetLanguage: string diff --git a/apps/sim/blocks/blocks/twilio.ts b/apps/sim/blocks/blocks/twilio.ts index 6d08dd02b1..c20bcfcca9 100644 --- a/apps/sim/blocks/blocks/twilio.ts +++ b/apps/sim/blocks/blocks/twilio.ts @@ -1,6 +1,6 @@ import { TwilioIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { TwilioSMSBlockOutput } from '@/tools/twilio/types' -import type { BlockConfig } from '../types' export const TwilioSMSBlock: BlockConfig = { type: 'twilio_sms', diff --git a/apps/sim/blocks/blocks/typeform.ts b/apps/sim/blocks/blocks/typeform.ts index a870311e26..544a6c74db 100644 --- a/apps/sim/blocks/blocks/typeform.ts +++ b/apps/sim/blocks/blocks/typeform.ts @@ -1,78 +1,6 @@ import { TypeformIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' - -interface TypeformResponse extends ToolResponse { - output: - | { - total_items: number - page_count: number - items: Array<{ - landing_id: string - token: string - landed_at: string - submitted_at: string - metadata: { - user_agent: string - platform: string - referer: string - network_id: string - browser: string - } - answers: Array<{ - field: { - id: string - type: string - ref: string - } - type: string - [key: string]: any // For different answer types (text, boolean, number, etc.) - }> - hidden: Record - calculated: { - score: number - } - variables: Array<{ - key: string - type: string - [key: string]: any // For different variable types - }> - }> - } - | { - fileUrl: string - contentType: string - filename: string - } - | { - fields: Array<{ - dropoffs: number - id: string - label: string - ref: string - title: string - type: string - views: number - }> - form: { - platforms: Array<{ - average_time: number - completion_rate: number - platform: string - responses_count: number - total_visits: number - unique_visits: number - }> - summary: { - average_time: number - completion_rate: number - responses_count: number - total_visits: number - unique_visits: number - } - } - } -} +import type { BlockConfig } from '@/blocks/types' +import type { TypeformResponse } from '@/tools/typeform/types' export const TypeformBlock: BlockConfig = { type: 'typeform', diff --git a/apps/sim/blocks/blocks/vision.ts b/apps/sim/blocks/blocks/vision.ts index a06cf762ff..6f012449c7 100644 --- a/apps/sim/blocks/blocks/vision.ts +++ b/apps/sim/blocks/blocks/vision.ts @@ -1,6 +1,6 @@ import { EyeIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { VisionResponse } from '@/tools/vision/types' -import type { BlockConfig } from '../types' export const VisionBlock: BlockConfig = { type: 'vision', diff --git a/apps/sim/blocks/blocks/wealthbox.ts b/apps/sim/blocks/blocks/wealthbox.ts index 8171635aff..2ea67c34c6 100644 --- a/apps/sim/blocks/blocks/wealthbox.ts +++ b/apps/sim/blocks/blocks/wealthbox.ts @@ -1,8 +1,6 @@ import { WealthboxIcon } from '@/components/icons' -import type { WealthboxReadResponse, WealthboxWriteResponse } from '@/tools/wealthbox/types' -import type { BlockConfig } from '../types' - -type WealthboxResponse = WealthboxReadResponse | WealthboxWriteResponse +import type { BlockConfig } from '@/blocks/types' +import type { WealthboxResponse } from '@/tools/wealthbox/types' export const WealthboxBlock: BlockConfig = { type: 'wealthbox', @@ -56,17 +54,37 @@ export const WealthboxBlock: BlockConfig = { requiredScopes: ['login', 'data'], layout: 'full', placeholder: 'Enter Contact ID', + mode: 'basic', + condition: { field: 'operation', value: ['read_contact', 'write_task', 'write_note'] }, + }, + { + id: 'manualContactId', + title: 'Contact ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Contact ID', + mode: 'advanced', condition: { field: 'operation', value: ['read_contact', 'write_task', 'write_note'] }, }, { id: 'taskId', title: 'Select Task', - type: 'short-input', + type: 'file-selector', provider: 'wealthbox', serviceId: 'wealthbox', requiredScopes: ['login', 'data'], layout: 'full', placeholder: 'Enter Task ID', + mode: 'basic', + condition: { field: 'operation', value: ['read_task'] }, + }, + { + id: 'manualTaskId', + title: 'Task ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter Task ID', + mode: 'advanced', condition: { field: 'operation', value: ['read_task'] }, }, { @@ -155,7 +173,14 @@ export const WealthboxBlock: BlockConfig = { } }, params: (params) => { - const { credential, operation, ...rest } = params + const { credential, operation, contactId, manualContactId, taskId, manualTaskId, ...rest } = + params + + // Handle contact ID input (selector or manual) + const effectiveContactId = (contactId || manualContactId || '').trim() + + // Handle task ID input (selector or manual) + const effectiveTaskId = (taskId || manualTaskId || '').trim() // Build the parameters based on operation type const baseParams = { @@ -168,25 +193,40 @@ export const WealthboxBlock: BlockConfig = { return { ...baseParams, noteId: params.noteId, + contactId: effectiveContactId, } } // For contact operations, we need contactId if (operation === 'read_contact') { - if (!params.contactId) { + if (!effectiveContactId) { throw new Error('Contact ID is required for contact operations') } return { ...baseParams, - contactId: params.contactId, + contactId: effectiveContactId, } } // For task operations, we need taskId if (operation === 'read_task') { + if (!effectiveTaskId) { + throw new Error('Task ID is required for task operations') + } + return { + ...baseParams, + taskId: effectiveTaskId, + } + } + + // For write_task and write_note operations, we need contactId + if (operation === 'write_task' || operation === 'write_note') { + if (!effectiveContactId) { + throw new Error('Contact ID is required for this operation') + } return { ...baseParams, - taskId: params.taskId, + contactId: effectiveContactId, } } @@ -199,7 +239,9 @@ export const WealthboxBlock: BlockConfig = { credential: { type: 'string', required: true }, noteId: { type: 'string', required: false }, contactId: { type: 'string', required: false }, + manualContactId: { type: 'string', required: false }, taskId: { type: 'string', required: false }, + manualTaskId: { type: 'string', required: false }, content: { type: 'string', required: false }, firstName: { type: 'string', required: false }, lastName: { type: 'string', required: false }, diff --git a/apps/sim/blocks/blocks/whatsapp.ts b/apps/sim/blocks/blocks/whatsapp.ts index 47485883c9..f9687bb4a2 100644 --- a/apps/sim/blocks/blocks/whatsapp.ts +++ b/apps/sim/blocks/blocks/whatsapp.ts @@ -1,16 +1,8 @@ import { WhatsAppIcon } from '@/components/icons' -import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' +import type { BlockConfig } from '@/blocks/types' +import type { WhatsAppResponse } from '@/tools/whatsapp/types' -interface WhatsAppBlockOutput extends ToolResponse { - output: { - success: boolean - messageId?: string - error?: string - } -} - -export const WhatsAppBlock: BlockConfig = { +export const WhatsAppBlock: BlockConfig = { type: 'whatsapp', name: 'WhatsApp', description: 'Send WhatsApp messages', diff --git a/apps/sim/blocks/blocks/workflow.ts b/apps/sim/blocks/blocks/workflow.ts index fac3ae58e3..536763410a 100644 --- a/apps/sim/blocks/blocks/workflow.ts +++ b/apps/sim/blocks/blocks/workflow.ts @@ -1,8 +1,8 @@ import { WorkflowIcon } from '@/components/icons' import { createLogger } from '@/lib/logs/console-logger' +import type { BlockConfig } from '@/blocks/types' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import type { ToolResponse } from '@/tools/types' -import type { BlockConfig } from '../types' const logger = createLogger('WorkflowBlock') diff --git a/apps/sim/blocks/blocks/x.ts b/apps/sim/blocks/blocks/x.ts index 24d3140d05..398565a003 100644 --- a/apps/sim/blocks/blocks/x.ts +++ b/apps/sim/blocks/blocks/x.ts @@ -1,8 +1,6 @@ import { xIcon } from '@/components/icons' -import type { XReadResponse, XSearchResponse, XUserResponse, XWriteResponse } from '@/tools/x/types' -import type { BlockConfig } from '../types' - -type XResponse = XWriteResponse | XReadResponse | XSearchResponse | XUserResponse +import type { BlockConfig } from '@/blocks/types' +import type { XResponse } from '@/tools/x/types' export const XBlock: BlockConfig = { type: 'x', @@ -15,7 +13,6 @@ export const XBlock: BlockConfig = { bgColor: '#000000', // X's black color icon: xIcon, subBlocks: [ - // Operation selector { id: 'operation', title: 'Operation', @@ -29,7 +26,6 @@ export const XBlock: BlockConfig = { ], value: () => 'x_write', }, - // X OAuth Authentication { id: 'credential', title: 'X Account', @@ -40,7 +36,6 @@ export const XBlock: BlockConfig = { requiredScopes: ['tweet.read', 'tweet.write', 'users.read'], placeholder: 'Select X account', }, - // Write operation inputs { id: 'text', title: 'Tweet Text', @@ -65,7 +60,6 @@ export const XBlock: BlockConfig = { placeholder: 'Enter comma-separated media IDs', condition: { field: 'operation', value: 'x_write' }, }, - // Read operation inputs { id: 'tweetId', title: 'Tweet ID', @@ -86,7 +80,6 @@ export const XBlock: BlockConfig = { value: () => 'false', condition: { field: 'operation', value: 'x_read' }, }, - // Search operation inputs { id: 'query', title: 'Search Query', @@ -131,7 +124,6 @@ export const XBlock: BlockConfig = { placeholder: 'YYYY-MM-DDTHH:mm:ssZ', condition: { field: 'operation', value: 'x_search' }, }, - // User operation inputs { id: 'username', title: 'Username', @@ -198,21 +190,17 @@ export const XBlock: BlockConfig = { inputs: { operation: { type: 'string', required: true }, credential: { type: 'string', required: true }, - // Write operation text: { type: 'string', required: false }, replyTo: { type: 'string', required: false }, mediaIds: { type: 'string', required: false }, poll: { type: 'json', required: false }, - // Read operation tweetId: { type: 'string', required: false }, includeReplies: { type: 'boolean', required: false }, - // Search operation query: { type: 'string', required: false }, maxResults: { type: 'number', required: false }, startTime: { type: 'string', required: false }, endTime: { type: 'string', required: false }, sortOrder: { type: 'string', required: false }, - // User operation username: { type: 'string', required: false }, includeRecentTweets: { type: 'boolean', required: false }, }, diff --git a/apps/sim/blocks/blocks/youtube.ts b/apps/sim/blocks/blocks/youtube.ts index 5ff45080d4..b736892228 100644 --- a/apps/sim/blocks/blocks/youtube.ts +++ b/apps/sim/blocks/blocks/youtube.ts @@ -1,6 +1,6 @@ import { YouTubeIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' import type { YouTubeSearchResponse } from '@/tools/youtube/types' -import type { BlockConfig } from '../types' export const YouTubeBlock: BlockConfig = { type: 'youtube', diff --git a/apps/sim/blocks/index.ts b/apps/sim/blocks/index.ts index d46c9ebcdc..f23585b2fd 100644 --- a/apps/sim/blocks/index.ts +++ b/apps/sim/blocks/index.ts @@ -5,8 +5,8 @@ import { getBlocksByCategory, isValidBlockType, registry, -} from './registry' +} from '@/blocks/registry' export { registry, getBlock, getBlocksByCategory, getAllBlockTypes, isValidBlockType, getAllBlocks } -export type { BlockConfig } from './types' +export type { BlockConfig } from '@/blocks/types' diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index eebfa23cde..467071b1be 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -3,67 +3,67 @@ * */ // Import all blocks directly here -import { AgentBlock } from './blocks/agent' -import { AirtableBlock } from './blocks/airtable' -import { ApiBlock } from './blocks/api' -import { BrowserUseBlock } from './blocks/browser_use' -import { ClayBlock } from './blocks/clay' -import { ConditionBlock } from './blocks/condition' -import { ConfluenceBlock } from './blocks/confluence' -import { DiscordBlock } from './blocks/discord' -import { ElevenLabsBlock } from './blocks/elevenlabs' -import { EvaluatorBlock } from './blocks/evaluator' -import { ExaBlock } from './blocks/exa' -import { FileBlock } from './blocks/file' -import { FirecrawlBlock } from './blocks/firecrawl' -import { FunctionBlock } from './blocks/function' -import { GitHubBlock } from './blocks/github' -import { GmailBlock } from './blocks/gmail' -import { GoogleSearchBlock } from './blocks/google' -import { GoogleCalendarBlock } from './blocks/google_calendar' -import { GoogleDocsBlock } from './blocks/google_docs' -import { GoogleDriveBlock } from './blocks/google_drive' -import { GoogleSheetsBlock } from './blocks/google_sheets' -import { HuggingFaceBlock } from './blocks/huggingface' -import { ImageGeneratorBlock } from './blocks/image_generator' -import { JinaBlock } from './blocks/jina' -import { JiraBlock } from './blocks/jira' -import { KnowledgeBlock } from './blocks/knowledge' -import { LinearBlock } from './blocks/linear' -import { LinkupBlock } from './blocks/linkup' -import { Mem0Block } from './blocks/mem0' -import { MemoryBlock } from './blocks/memory' -import { MicrosoftExcelBlock } from './blocks/microsoft_excel' -import { MicrosoftTeamsBlock } from './blocks/microsoft_teams' -import { MistralParseBlock } from './blocks/mistral_parse' -import { NotionBlock } from './blocks/notion' -import { OpenAIBlock } from './blocks/openai' -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' -import { SlackBlock } from './blocks/slack' -import { StagehandBlock } from './blocks/stagehand' -import { StagehandAgentBlock } from './blocks/stagehand_agent' -import { StarterBlock } from './blocks/starter' -import { SupabaseBlock } from './blocks/supabase' -import { TavilyBlock } from './blocks/tavily' -import { TelegramBlock } from './blocks/telegram' -import { ThinkingBlock } from './blocks/thinking' -import { TranslateBlock } from './blocks/translate' -import { TwilioSMSBlock } from './blocks/twilio' -import { TypeformBlock } from './blocks/typeform' -import { VisionBlock } from './blocks/vision' -import { WealthboxBlock } from './blocks/wealthbox' -import { WhatsAppBlock } from './blocks/whatsapp' -import { WorkflowBlock } from './blocks/workflow' -import { XBlock } from './blocks/x' -import { YouTubeBlock } from './blocks/youtube' -import type { BlockConfig } from './types' +import { AgentBlock } from '@/blocks/blocks/agent' +import { AirtableBlock } from '@/blocks/blocks/airtable' +import { ApiBlock } from '@/blocks/blocks/api' +import { BrowserUseBlock } from '@/blocks/blocks/browser_use' +import { ClayBlock } from '@/blocks/blocks/clay' +import { ConditionBlock } from '@/blocks/blocks/condition' +import { ConfluenceBlock } from '@/blocks/blocks/confluence' +import { DiscordBlock } from '@/blocks/blocks/discord' +import { ElevenLabsBlock } from '@/blocks/blocks/elevenlabs' +import { EvaluatorBlock } from '@/blocks/blocks/evaluator' +import { ExaBlock } from '@/blocks/blocks/exa' +import { FileBlock } from '@/blocks/blocks/file' +import { FirecrawlBlock } from '@/blocks/blocks/firecrawl' +import { FunctionBlock } from '@/blocks/blocks/function' +import { GitHubBlock } from '@/blocks/blocks/github' +import { GmailBlock } from '@/blocks/blocks/gmail' +import { GoogleSearchBlock } from '@/blocks/blocks/google' +import { GoogleCalendarBlock } from '@/blocks/blocks/google_calendar' +import { GoogleDocsBlock } from '@/blocks/blocks/google_docs' +import { GoogleDriveBlock } from '@/blocks/blocks/google_drive' +import { GoogleSheetsBlock } from '@/blocks/blocks/google_sheets' +import { HuggingFaceBlock } from '@/blocks/blocks/huggingface' +import { ImageGeneratorBlock } from '@/blocks/blocks/image_generator' +import { JinaBlock } from '@/blocks/blocks/jina' +import { JiraBlock } from '@/blocks/blocks/jira' +import { KnowledgeBlock } from '@/blocks/blocks/knowledge' +import { LinearBlock } from '@/blocks/blocks/linear' +import { LinkupBlock } from '@/blocks/blocks/linkup' +import { Mem0Block } from '@/blocks/blocks/mem0' +import { MemoryBlock } from '@/blocks/blocks/memory' +import { MicrosoftExcelBlock } from '@/blocks/blocks/microsoft_excel' +import { MicrosoftTeamsBlock } from '@/blocks/blocks/microsoft_teams' +import { MistralParseBlock } from '@/blocks/blocks/mistral_parse' +import { NotionBlock } from '@/blocks/blocks/notion' +import { OpenAIBlock } from '@/blocks/blocks/openai' +import { OutlookBlock } from '@/blocks/blocks/outlook' +import { PerplexityBlock } from '@/blocks/blocks/perplexity' +import { PineconeBlock } from '@/blocks/blocks/pinecone' +import { RedditBlock } from '@/blocks/blocks/reddit' +import { ResponseBlock } from '@/blocks/blocks/response' +import { RouterBlock } from '@/blocks/blocks/router' +import { S3Block } from '@/blocks/blocks/s3' +import { SerperBlock } from '@/blocks/blocks/serper' +import { SlackBlock } from '@/blocks/blocks/slack' +import { StagehandBlock } from '@/blocks/blocks/stagehand' +import { StagehandAgentBlock } from '@/blocks/blocks/stagehand_agent' +import { StarterBlock } from '@/blocks/blocks/starter' +import { SupabaseBlock } from '@/blocks/blocks/supabase' +import { TavilyBlock } from '@/blocks/blocks/tavily' +import { TelegramBlock } from '@/blocks/blocks/telegram' +import { ThinkingBlock } from '@/blocks/blocks/thinking' +import { TranslateBlock } from '@/blocks/blocks/translate' +import { TwilioSMSBlock } from '@/blocks/blocks/twilio' +import { TypeformBlock } from '@/blocks/blocks/typeform' +import { VisionBlock } from '@/blocks/blocks/vision' +import { WealthboxBlock } from '@/blocks/blocks/wealthbox' +import { WhatsAppBlock } from '@/blocks/blocks/whatsapp' +import { WorkflowBlock } from '@/blocks/blocks/workflow' +import { XBlock } from '@/blocks/blocks/x' +import { YouTubeBlock } from '@/blocks/blocks/youtube' +import type { BlockConfig } from '@/blocks/types' // Registry of all available blocks, alphabetically sorted export const registry: Record = { diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 8e2b475b2b..36eed84bdc 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2808,7 +2808,7 @@ export const ResponseIcon = (props: SVGProps) => ( > ) diff --git a/apps/sim/executor/__test-utils__/mock-dependencies.ts b/apps/sim/executor/__test-utils__/mock-dependencies.ts index b8005878b5..7266a6aadb 100644 --- a/apps/sim/executor/__test-utils__/mock-dependencies.ts +++ b/apps/sim/executor/__test-utils__/mock-dependencies.ts @@ -12,6 +12,11 @@ vi.mock('@/lib/logs/console-logger', () => ({ })), })) +// Blocks +vi.mock('@/blocks/index', () => ({ + getBlock: vi.fn(), +})) + // Tools vi.mock('@/tools/utils', () => ({ getTool: vi.fn(), diff --git a/apps/sim/executor/resolver/resolver.test.ts b/apps/sim/executor/resolver/resolver.test.ts index 81b34e9bd9..ae63c0b61c 100644 --- a/apps/sim/executor/resolver/resolver.test.ts +++ b/apps/sim/executor/resolver/resolver.test.ts @@ -1,19 +1,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { getBlock } from '@/blocks/index' import { BlockType } from '@/executor/consts' import { InputResolver } from '@/executor/resolver/resolver' import type { ExecutionContext } from '@/executor/types' import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types' -// Mock logger -vi.mock('@/lib/logs/console-logger', () => ({ - createLogger: vi.fn().mockReturnValue({ - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - }), -})) - describe('InputResolver', () => { let sampleWorkflow: SerializedWorkflow let mockContext: any @@ -1898,4 +1889,502 @@ describe('InputResolver', () => { expect(() => loopResolver.resolveInputs(testBlock, loopContext)).not.toThrow() }) }) + + describe('Conditional Input Filtering', () => { + const mockGetBlock = getBlock as ReturnType + + afterEach(() => { + mockGetBlock.mockReset() + }) + + it('should filter inputs based on operation conditions for Knowledge block', () => { + // Mock the Knowledge block configuration + mockGetBlock.mockReturnValue({ + type: 'knowledge', + subBlocks: [ + { + id: 'operation', + type: 'dropdown', + options: [ + { label: 'Search', id: 'search' }, + { label: 'Upload Chunk', id: 'upload_chunk' }, + ], + }, + { + id: 'query', + type: 'short-input', + condition: { field: 'operation', value: 'search' }, + }, + { + id: 'knowledgeBaseIds', + type: 'knowledge-base-selector', + condition: { field: 'operation', value: 'search' }, + }, + { + id: 'documentId', + type: 'document-selector', + condition: { field: 'operation', value: 'upload_chunk' }, + }, + { + id: 'content', + type: 'long-input', + condition: { field: 'operation', value: 'upload_chunk' }, + }, + ], + }) + + // Create a Knowledge block with upload_chunk operation + const knowledgeBlock: SerializedBlock = { + id: 'knowledge-block', + metadata: { id: 'knowledge', name: 'Knowledge Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'knowledge', + params: { + operation: 'upload_chunk', + query: '', // This should be filtered out + knowledgeBaseIds: 'kb-1', // This should be filtered out + documentId: 'doc-1', // This should be included + content: 'chunk content', // This should be included + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result = resolver.resolveInputs(knowledgeBlock, mockContext) + + // Should only include inputs for upload_chunk operation + expect(result).toHaveProperty('operation', 'upload_chunk') + expect(result).toHaveProperty('documentId', 'doc-1') + expect(result).toHaveProperty('content', 'chunk content') + + // Should NOT include inputs for search operation + expect(result).not.toHaveProperty('query') + expect(result).not.toHaveProperty('knowledgeBaseIds') + }) + + it('should filter inputs based on operation conditions for Knowledge block search operation', () => { + // Mock the Knowledge block configuration + mockGetBlock.mockReturnValue({ + type: 'knowledge', + subBlocks: [ + { + id: 'operation', + type: 'dropdown', + options: [ + { label: 'Search', id: 'search' }, + { label: 'Upload Chunk', id: 'upload_chunk' }, + ], + }, + { + id: 'query', + type: 'short-input', + condition: { field: 'operation', value: 'search' }, + }, + { + id: 'knowledgeBaseIds', + type: 'knowledge-base-selector', + condition: { field: 'operation', value: 'search' }, + }, + { + id: 'documentId', + type: 'document-selector', + condition: { field: 'operation', value: 'upload_chunk' }, + }, + { + id: 'content', + type: 'long-input', + condition: { field: 'operation', value: 'upload_chunk' }, + }, + ], + }) + + // Create a Knowledge block with search operation + const knowledgeBlock: SerializedBlock = { + id: 'knowledge-block', + metadata: { id: 'knowledge', name: 'Knowledge Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'knowledge', + params: { + operation: 'search', + query: 'search query', + knowledgeBaseIds: 'kb-1', + documentId: 'doc-1', // This should be filtered out + content: 'chunk content', // This should be filtered out + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result = resolver.resolveInputs(knowledgeBlock, mockContext) + + // Should only include inputs for search operation + expect(result).toHaveProperty('operation', 'search') + expect(result).toHaveProperty('query', 'search query') + expect(result).toHaveProperty('knowledgeBaseIds', 'kb-1') + + // Should NOT include inputs for upload_chunk operation + expect(result).not.toHaveProperty('documentId') + expect(result).not.toHaveProperty('content') + }) + + it('should handle array conditions correctly', () => { + // Mock a block with array condition + mockGetBlock.mockReturnValue({ + type: 'test-block', + subBlocks: [ + { + id: 'operation', + type: 'dropdown', + options: [ + { label: 'Create', id: 'create' }, + { label: 'Update', id: 'update' }, + { label: 'Delete', id: 'delete' }, + ], + }, + { + id: 'data', + type: 'long-input', + condition: { field: 'operation', value: ['create', 'update'] }, + }, + { + id: 'id', + type: 'short-input', + condition: { field: 'operation', value: ['update', 'delete'] }, + }, + ], + }) + + const testBlock: SerializedBlock = { + id: 'test-block', + metadata: { id: 'test-block', name: 'Test Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'test-block', + params: { + operation: 'update', + data: 'some data', + id: 'item-1', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result = resolver.resolveInputs(testBlock, mockContext) + + // Should include inputs for update operation (both data and id) + expect(result).toHaveProperty('operation', 'update') + expect(result).toHaveProperty('data', 'some data') + expect(result).toHaveProperty('id', 'item-1') + }) + + it('should include all inputs when no conditions are present', () => { + // Mock a block with no conditions + mockGetBlock.mockReturnValue({ + type: 'simple-block', + subBlocks: [ + { + id: 'param1', + type: 'short-input', + }, + { + id: 'param2', + type: 'long-input', + }, + ], + }) + + const simpleBlock: SerializedBlock = { + id: 'simple-block', + metadata: { id: 'simple-block', name: 'Simple Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'simple-block', + params: { + param1: 'value1', + param2: 'value2', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result = resolver.resolveInputs(simpleBlock, mockContext) + + // Should include all inputs + expect(result).toHaveProperty('param1', 'value1') + expect(result).toHaveProperty('param2', 'value2') + }) + + it('should return all inputs when block config is not found', () => { + // Mock getBlock to return undefined + mockGetBlock.mockReturnValue(undefined) + + const unknownBlock: SerializedBlock = { + id: 'unknown-block', + metadata: { id: 'unknown-type', name: 'Unknown Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'unknown-type', + params: { + param1: 'value1', + param2: 'value2', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result = resolver.resolveInputs(unknownBlock, mockContext) + + // Should include all inputs when block config is not found + expect(result).toHaveProperty('param1', 'value1') + expect(result).toHaveProperty('param2', 'value2') + }) + + it('should handle negated conditions correctly', () => { + // Mock a block with negated condition + mockGetBlock.mockReturnValue({ + type: 'test-block', + subBlocks: [ + { + id: 'operation', + type: 'dropdown', + options: [ + { label: 'Create', id: 'create' }, + { label: 'Delete', id: 'delete' }, + ], + }, + { + id: 'confirmationField', + type: 'short-input', + condition: { field: 'operation', value: 'create', not: true }, + }, + ], + }) + + const testBlock: SerializedBlock = { + id: 'test-block', + metadata: { id: 'test-block', name: 'Test Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'test-block', + params: { + operation: 'delete', + confirmationField: 'confirmed', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result = resolver.resolveInputs(testBlock, mockContext) + + // Should include confirmationField because operation is NOT 'create' + expect(result).toHaveProperty('operation', 'delete') + expect(result).toHaveProperty('confirmationField', 'confirmed') + }) + + it('should handle compound AND conditions correctly', () => { + // Mock a block with compound AND condition + mockGetBlock.mockReturnValue({ + type: 'test-block', + subBlocks: [ + { + id: 'operation', + type: 'dropdown', + options: [ + { label: 'Create', id: 'create' }, + { label: 'Update', id: 'update' }, + ], + }, + { + id: 'enabled', + type: 'switch', + }, + { + id: 'specialField', + type: 'short-input', + condition: { + field: 'operation', + value: 'update', + and: { field: 'enabled', value: true }, + }, + }, + ], + }) + + const testBlock: SerializedBlock = { + id: 'test-block', + metadata: { id: 'test-block', name: 'Test Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'test-block', + params: { + operation: 'update', + enabled: true, + specialField: 'special value', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result = resolver.resolveInputs(testBlock, mockContext) + + // Should include specialField because operation is 'update' AND enabled is true + expect(result).toHaveProperty('operation', 'update') + expect(result).toHaveProperty('enabled', true) + expect(result).toHaveProperty('specialField', 'special value') + }) + + it('should always include inputs without conditions', () => { + // Mock a block with mixed conditions + mockGetBlock.mockReturnValue({ + type: 'test-block', + subBlocks: [ + { + id: 'operation', + type: 'dropdown', + // No condition - should always be included + }, + { + id: 'alwaysVisible', + type: 'short-input', + // No condition - should always be included + }, + { + id: 'conditionalField', + type: 'short-input', + condition: { field: 'operation', value: 'search' }, + }, + ], + }) + + const testBlock: SerializedBlock = { + id: 'test-block', + metadata: { id: 'test-block', name: 'Test Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'test-block', + params: { + operation: 'upload', + alwaysVisible: 'always here', + conditionalField: 'should be filtered out', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result = resolver.resolveInputs(testBlock, mockContext) + + // Should include inputs without conditions + expect(result).toHaveProperty('operation', 'upload') + expect(result).toHaveProperty('alwaysVisible', 'always here') + + // Should NOT include conditional field that doesn't match + expect(result).not.toHaveProperty('conditionalField') + }) + + it('should handle duplicate field names with different conditions (Knowledge block case)', () => { + // Mock Knowledge block with duplicate content fields + mockGetBlock.mockReturnValue({ + type: 'knowledge', + subBlocks: [ + { + id: 'operation', + type: 'dropdown', + }, + { + id: 'content', + title: 'Chunk Content', + type: 'long-input', + condition: { field: 'operation', value: 'upload_chunk' }, + }, + { + id: 'content', + title: 'Document Content', + type: 'long-input', + condition: { field: 'operation', value: 'create_document' }, + }, + ], + }) + + // Test upload_chunk operation + const uploadChunkBlock: SerializedBlock = { + id: 'knowledge-block', + metadata: { id: 'knowledge', name: 'Knowledge Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'knowledge', + params: { + operation: 'upload_chunk', + content: 'chunk content here', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result1 = resolver.resolveInputs(uploadChunkBlock, mockContext) + expect(result1).toHaveProperty('operation', 'upload_chunk') + expect(result1).toHaveProperty('content', 'chunk content here') + + // Test create_document operation + const createDocBlock: SerializedBlock = { + id: 'knowledge-block', + metadata: { id: 'knowledge', name: 'Knowledge Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'knowledge', + params: { + operation: 'create_document', + content: 'document content here', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result2 = resolver.resolveInputs(createDocBlock, mockContext) + expect(result2).toHaveProperty('operation', 'create_document') + expect(result2).toHaveProperty('content', 'document content here') + + // Test search operation (should NOT include content) + const searchBlock: SerializedBlock = { + id: 'knowledge-block', + metadata: { id: 'knowledge', name: 'Knowledge Block' }, + position: { x: 0, y: 0 }, + config: { + tool: 'knowledge', + params: { + operation: 'search', + content: 'should be filtered out', + }, + }, + inputs: {}, + outputs: {}, + enabled: true, + } + + const result3 = resolver.resolveInputs(searchBlock, mockContext) + expect(result3).toHaveProperty('operation', 'search') + expect(result3).not.toHaveProperty('content') + }) + }) }) diff --git a/apps/sim/executor/resolver/resolver.ts b/apps/sim/executor/resolver/resolver.ts index 0e4d926148..41a848d473 100644 --- a/apps/sim/executor/resolver/resolver.ts +++ b/apps/sim/executor/resolver/resolver.ts @@ -1,6 +1,7 @@ import { BlockPathCalculator } from '@/lib/block-path-calculator' import { createLogger } from '@/lib/logs/console-logger' import { VariableManager } from '@/lib/variables/variable-manager' +import { getBlock } from '@/blocks/index' import type { LoopManager } from '@/executor/loops/loops' import type { ExecutionContext } from '@/executor/types' import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types' @@ -55,6 +56,102 @@ export class InputResolver { } } + /** + * Evaluates if a sub-block should be active based on its condition + * @param condition - The condition to evaluate + * @param currentValues - Current values of all inputs + * @returns True if the sub-block should be active + */ + private evaluateSubBlockCondition( + condition: + | { + field: string + value: any + not?: boolean + and?: { field: string; value: any; not?: boolean } + } + | undefined, + currentValues: Record + ): boolean { + if (!condition) return true + + // Get the field value + const fieldValue = currentValues[condition.field] + + // Check if the condition value is an array + const isValueMatch = Array.isArray(condition.value) + ? fieldValue != null && + (condition.not + ? !condition.value.includes(fieldValue) + : condition.value.includes(fieldValue)) + : condition.not + ? fieldValue !== condition.value + : fieldValue === condition.value + + // Check both conditions if 'and' is present + const isAndValueMatch = + !condition.and || + (() => { + const andFieldValue = currentValues[condition.and!.field] + return Array.isArray(condition.and!.value) + ? andFieldValue != null && + (condition.and!.not + ? !condition.and!.value.includes(andFieldValue) + : condition.and!.value.includes(andFieldValue)) + : condition.and!.not + ? andFieldValue !== condition.and!.value + : andFieldValue === condition.and!.value + })() + + return isValueMatch && isAndValueMatch + } + + /** + * Filters inputs based on sub-block conditions + * @param block - Block to filter inputs for + * @param inputs - All input parameters + * @returns Filtered input parameters that should be processed + */ + private filterInputsByConditions( + block: SerializedBlock, + inputs: Record + ): Record { + const blockType = block.metadata?.id + if (!blockType) return inputs + + const blockConfig = getBlock(blockType) + if (!blockConfig || !blockConfig.subBlocks) return inputs + + // Filter inputs based on conditions + const filteredInputs: Record = {} + for (const [key, value] of Object.entries(inputs)) { + // Check if this input should be included based on subBlock conditions + let shouldInclude = false + + // Find all subBlocks with this ID + const matchingSubBlocks = blockConfig.subBlocks.filter((sb) => sb.id === key) + + if (matchingSubBlocks.length === 0) { + // No subBlock config found for this input - include it + shouldInclude = true + } else { + // Check if any of the matching subBlocks should be active + for (const subBlock of matchingSubBlocks) { + if (!subBlock.condition || this.evaluateSubBlockCondition(subBlock.condition, inputs)) { + shouldInclude = true + break + } + } + } + + if (shouldInclude) { + filteredInputs[key] = value + } + } + + return filteredInputs + } + /** * Resolves all inputs for a block based on current context. * Handles block references, environment variables, and JSON parsing. @@ -64,7 +161,9 @@ export class InputResolver { * @returns Resolved input parameters */ resolveInputs(block: SerializedBlock, context: ExecutionContext): Record { - const inputs = { ...block.config.params } + const allInputs = { ...block.config.params } + // Filter inputs based on sub-block conditions to only process active fields + const inputs = this.filterInputsByConditions(block, allInputs) const result: Record = {} // Process each input parameter for (const [key, value] of Object.entries(inputs)) { diff --git a/apps/sim/tools/airtable/types.ts b/apps/sim/tools/airtable/types.ts index 64914c3986..3408fdee04 100644 --- a/apps/sim/tools/airtable/types.ts +++ b/apps/sim/tools/airtable/types.ts @@ -87,3 +87,10 @@ export interface AirtableUpdateMultipleResponse extends ToolResponse { } } } + +export type AirtableResponse = + | AirtableListResponse + | AirtableGetResponse + | AirtableCreateResponse + | AirtableUpdateResponse + | AirtableUpdateMultipleResponse diff --git a/apps/sim/tools/browser_use/types.ts b/apps/sim/tools/browser_use/types.ts index 24406b179d..9c74108d3d 100644 --- a/apps/sim/tools/browser_use/types.ts +++ b/apps/sim/tools/browser_use/types.ts @@ -27,3 +27,12 @@ export interface BrowserUseTaskOutput { export interface BrowserUseRunTaskResponse extends ToolResponse { output: BrowserUseTaskOutput } + +export interface BrowserUseResponse extends ToolResponse { + output: { + id: string + success: boolean + output: any + steps: BrowserUseTaskStep[] + } +} diff --git a/apps/sim/tools/confluence/types.ts b/apps/sim/tools/confluence/types.ts index 4738c8b18e..9c71b1e8b4 100644 --- a/apps/sim/tools/confluence/types.ts +++ b/apps/sim/tools/confluence/types.ts @@ -42,3 +42,5 @@ export interface ConfluenceUpdateResponse extends ToolResponse { success: boolean } } + +export type ConfluenceResponse = ConfluenceRetrieveResponse | ConfluenceUpdateResponse diff --git a/apps/sim/tools/elevenlabs/types.ts b/apps/sim/tools/elevenlabs/types.ts index 577881ab43..0648ee48cf 100644 --- a/apps/sim/tools/elevenlabs/types.ts +++ b/apps/sim/tools/elevenlabs/types.ts @@ -12,3 +12,9 @@ export interface ElevenLabsTtsResponse extends ToolResponse { audioUrl: string } } + +export interface ElevenLabsBlockResponse extends ToolResponse { + output: { + audioUrl: string + } +} diff --git a/apps/sim/tools/exa/types.ts b/apps/sim/tools/exa/types.ts index ee25d55581..8a675ef557 100644 --- a/apps/sim/tools/exa/types.ts +++ b/apps/sim/tools/exa/types.ts @@ -88,3 +88,9 @@ export interface ExaAnswerResponse extends ToolResponse { }[] } } + +export type ExaResponse = + | ExaSearchResponse + | ExaGetContentsResponse + | ExaFindSimilarLinksResponse + | ExaAnswerResponse diff --git a/apps/sim/tools/firecrawl/types.ts b/apps/sim/tools/firecrawl/types.ts index 3cb5c579ec..910f463eca 100644 --- a/apps/sim/tools/firecrawl/types.ts +++ b/apps/sim/tools/firecrawl/types.ts @@ -58,3 +58,5 @@ export interface SearchResponse extends ToolResponse { warning?: string } } + +export type FirecrawlResponse = ScrapeResponse | SearchResponse diff --git a/apps/sim/tools/github/types.ts b/apps/sim/tools/github/types.ts index 27eb3c2e8a..e70c1d8b2b 100644 --- a/apps/sim/tools/github/types.ts +++ b/apps/sim/tools/github/types.ts @@ -147,3 +147,9 @@ export interface RepoInfoResponse extends ToolResponse { metadata: RepoMetadata } } + +export type GitHubResponse = + | PullRequestResponse + | CreateCommentResponse + | LatestCommitResponse + | RepoInfoResponse diff --git a/apps/sim/tools/google_calendar/types.ts b/apps/sim/tools/google_calendar/types.ts index c5687c2fec..6d43bdecdc 100644 --- a/apps/sim/tools/google_calendar/types.ts +++ b/apps/sim/tools/google_calendar/types.ts @@ -276,3 +276,11 @@ export interface GoogleCalendarApiListResponse { nextSyncToken?: string items: GoogleCalendarApiEventResponse[] } + +export type GoogleCalendarResponse = + | GoogleCalendarCreateResponse + | GoogleCalendarListResponse + | GoogleCalendarGetResponse + | GoogleCalendarQuickAddResponse + | GoogleCalendarInviteResponse + | GoogleCalendarUpdateResponse diff --git a/apps/sim/tools/google_docs/types.ts b/apps/sim/tools/google_docs/types.ts index aab22906be..44f484b68b 100644 --- a/apps/sim/tools/google_docs/types.ts +++ b/apps/sim/tools/google_docs/types.ts @@ -38,3 +38,8 @@ export interface GoogleDocsToolParams { folderId?: string folderSelector?: string } + +export type GoogleDocsResponse = + | GoogleDocsReadResponse + | GoogleDocsWriteResponse + | GoogleDocsCreateResponse diff --git a/apps/sim/tools/google_drive/types.ts b/apps/sim/tools/google_drive/types.ts index 7973ff11f8..cdbea5c04a 100644 --- a/apps/sim/tools/google_drive/types.ts +++ b/apps/sim/tools/google_drive/types.ts @@ -45,3 +45,8 @@ export interface GoogleDriveToolParams { pageToken?: string exportMimeType?: string } + +export type GoogleDriveResponse = + | GoogleDriveUploadResponse + | GoogleDriveGetContentResponse + | GoogleDriveListResponse diff --git a/apps/sim/tools/google_sheets/types.ts b/apps/sim/tools/google_sheets/types.ts index 5a16355710..e7401a8728 100644 --- a/apps/sim/tools/google_sheets/types.ts +++ b/apps/sim/tools/google_sheets/types.ts @@ -69,3 +69,9 @@ export interface GoogleSheetsToolParams { responseValueRenderOption?: 'FORMATTED_VALUE' | 'UNFORMATTED_VALUE' | 'FORMULA' majorDimension?: 'ROWS' | 'COLUMNS' } + +export type GoogleSheetsResponse = + | GoogleSheetsReadResponse + | GoogleSheetsWriteResponse + | GoogleSheetsUpdateResponse + | GoogleSheetsAppendResponse diff --git a/apps/sim/tools/jira/types.ts b/apps/sim/tools/jira/types.ts index 3714a54e5a..76d024b288 100644 --- a/apps/sim/tools/jira/types.ts +++ b/apps/sim/tools/jira/types.ts @@ -104,3 +104,9 @@ export interface JiraCloudResource { scopes: string[] avatarUrl: string } + +export type JiraResponse = + | JiraRetrieveResponse + | JiraUpdateResponse + | JiraWriteResponse + | JiraRetrieveResponseBulk diff --git a/apps/sim/tools/linear/types.ts b/apps/sim/tools/linear/types.ts index d91c05b9b4..71beefad4a 100644 --- a/apps/sim/tools/linear/types.ts +++ b/apps/sim/tools/linear/types.ts @@ -34,3 +34,5 @@ export interface LinearCreateIssueResponse extends ToolResponse { issue: LinearIssue } } + +export type LinearResponse = LinearReadIssuesResponse | LinearCreateIssueResponse diff --git a/apps/sim/tools/microsoft_excel/types.ts b/apps/sim/tools/microsoft_excel/types.ts index ad4c8f354d..f95bd52c5a 100644 --- a/apps/sim/tools/microsoft_excel/types.ts +++ b/apps/sim/tools/microsoft_excel/types.ts @@ -67,3 +67,8 @@ export interface MicrosoftExcelTableToolParams { values: ExcelCellValue[][] rowIndex?: number } + +export type MicrosoftExcelResponse = + | MicrosoftExcelReadResponse + | MicrosoftExcelWriteResponse + | MicrosoftExcelTableAddResponse diff --git a/apps/sim/tools/microsoft_teams/types.ts b/apps/sim/tools/microsoft_teams/types.ts index e0408c554f..72a59d4f95 100644 --- a/apps/sim/tools/microsoft_teams/types.ts +++ b/apps/sim/tools/microsoft_teams/types.ts @@ -57,3 +57,5 @@ export interface MicrosoftTeamsToolParams { teamId?: string content?: string } + +export type MicrosoftTeamsResponse = MicrosoftTeamsReadResponse | MicrosoftTeamsWriteResponse diff --git a/apps/sim/tools/outlook/types.ts b/apps/sim/tools/outlook/types.ts index 5ad7cae54f..4f71948a14 100644 --- a/apps/sim/tools/outlook/types.ts +++ b/apps/sim/tools/outlook/types.ts @@ -127,3 +127,5 @@ export interface CleanedOutlookMessage { isRead?: boolean importance?: string } + +export type OutlookResponse = OutlookReadResponse | OutlookSendResponse | OutlookDraftResponse diff --git a/apps/sim/tools/reddit/types.ts b/apps/sim/tools/reddit/types.ts index b7f8323a18..2b120fca07 100644 --- a/apps/sim/tools/reddit/types.ts +++ b/apps/sim/tools/reddit/types.ts @@ -74,3 +74,5 @@ export interface RedditCommentsResponse extends ToolResponse { comments: RedditComment[] } } + +export type RedditResponse = RedditHotPostsResponse | RedditPostsResponse | RedditCommentsResponse diff --git a/apps/sim/tools/slack/types.ts b/apps/sim/tools/slack/types.ts index 27649697fd..ce37dcac58 100644 --- a/apps/sim/tools/slack/types.ts +++ b/apps/sim/tools/slack/types.ts @@ -18,3 +18,5 @@ export interface SlackMessageResponse extends ToolResponse { channel: string } } + +export type SlackResponse = SlackMessageResponse diff --git a/apps/sim/tools/supabase/types.ts b/apps/sim/tools/supabase/types.ts index 92388050c6..9a62416dfe 100644 --- a/apps/sim/tools/supabase/types.ts +++ b/apps/sim/tools/supabase/types.ts @@ -13,18 +13,16 @@ export interface SupabaseInsertParams { data: any } -export interface SupabaseQueryResponse extends ToolResponse { - error?: string +export interface SupabaseBaseResponse extends ToolResponse { output: { message: string results: any } -} - -export interface SupabaseInsertResponse extends ToolResponse { error?: string - output: { - message: string - results: any - } } + +export interface SupabaseQueryResponse extends SupabaseBaseResponse {} + +export interface SupabaseInsertResponse extends SupabaseBaseResponse {} + +export interface SupabaseResponse extends SupabaseBaseResponse {} diff --git a/apps/sim/tools/tavily/types.ts b/apps/sim/tools/tavily/types.ts index 708503f2d3..4e0018068b 100644 --- a/apps/sim/tools/tavily/types.ts +++ b/apps/sim/tools/tavily/types.ts @@ -70,3 +70,5 @@ export interface SearchResponse extends ToolResponse { response_time: number } } + +export type TavilyResponse = TavilySearchResponse | TavilyExtractResponse diff --git a/apps/sim/tools/typeform/types.ts b/apps/sim/tools/typeform/types.ts index 6f71b7e661..d7562efbf1 100644 --- a/apps/sim/tools/typeform/types.ts +++ b/apps/sim/tools/typeform/types.ts @@ -103,3 +103,10 @@ export interface TypeformResponsesResponse extends ToolResponse { }> } } + +export interface TypeformResponse extends ToolResponse { + output: + | TypeformResponsesResponse['output'] + | TypeformFilesResponse['output'] + | TypeformInsightsData +} diff --git a/apps/sim/tools/wealthbox/types.ts b/apps/sim/tools/wealthbox/types.ts index b2a38041a2..420bb11ec6 100644 --- a/apps/sim/tools/wealthbox/types.ts +++ b/apps/sim/tools/wealthbox/types.ts @@ -140,3 +140,5 @@ export interface WealthboxWriteParams { category?: number priority?: 'Low' | 'Medium' | 'High' } + +export type WealthboxResponse = WealthboxReadResponse | WealthboxWriteResponse diff --git a/apps/sim/tools/whatsapp/send_message.ts b/apps/sim/tools/whatsapp/send_message.ts index df6c8c4dc0..0df7cc942b 100644 --- a/apps/sim/tools/whatsapp/send_message.ts +++ b/apps/sim/tools/whatsapp/send_message.ts @@ -1,10 +1,10 @@ import { createLogger } from '@/lib/logs/console-logger' import type { ToolConfig } from '../types' -import type { WhatsAppToolResponse } from './types' +import type { WhatsAppResponse, WhatsAppSendMessageParams } from './types' const logger = createLogger('WhatsAppSendMessageTool') -export const sendMessageTool: ToolConfig = { +export const sendMessageTool: ToolConfig = { id: 'whatsapp_send_message', name: 'WhatsApp', description: 'Send WhatsApp messages', diff --git a/apps/sim/tools/whatsapp/types.ts b/apps/sim/tools/whatsapp/types.ts index daf5239ee2..5db258fa0a 100644 --- a/apps/sim/tools/whatsapp/types.ts +++ b/apps/sim/tools/whatsapp/types.ts @@ -1,6 +1,13 @@ import type { ToolResponse } from '../types' -export interface WhatsAppToolResponse extends ToolResponse { +export interface WhatsAppSendMessageParams { + phoneNumber: string + message: string + phoneNumberId: string + accessToken: string +} + +export interface WhatsAppResponse extends ToolResponse { output: { success: boolean messageId?: string diff --git a/apps/sim/tools/x/types.ts b/apps/sim/tools/x/types.ts index 04d9e49a5c..62c52209ee 100644 --- a/apps/sim/tools/x/types.ts +++ b/apps/sim/tools/x/types.ts @@ -105,3 +105,5 @@ export interface XUserResponse extends ToolResponse { recentTweets?: XTweet[] } } + +export type XResponse = XWriteResponse | XReadResponse | XSearchResponse | XUserResponse