diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts index 2db80532508..6f9d22a7841 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts @@ -1,77 +1,18 @@ import { useCallback, useMemo } from 'react' -import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility' import { buildCanonicalIndex, evaluateSubBlockCondition, isSubBlockFeatureEnabled, isSubBlockHidden, isSubBlockVisibleForMode, - resolveDependencyValue, } from '@/lib/workflows/subblocks/visibility' import type { BlockConfig, SubBlockConfig, SubBlockType } from '@/blocks/types' -import { useWorkspaceCredential } from '@/hooks/queries/credentials' import { usePermissionConfig } from '@/hooks/use-permission-config' +import { useReactiveConditions } from '@/hooks/use-reactive-conditions' import { useWorkflowDiffStore } from '@/stores/workflow-diff' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { mergeSubblockState } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' -/** - * Evaluates reactive conditions for subblocks. Always calls the same hooks - * regardless of whether a reactive condition exists (Rules of Hooks). - * - * Returns a Set of subblock IDs that should be hidden. - */ -function useReactiveConditions( - subBlocks: SubBlockConfig[], - blockId: string, - activeWorkflowId: string | null, - canonicalModeOverrides?: CanonicalModeOverrides -): Set { - const reactiveSubBlock = useMemo(() => subBlocks.find((sb) => sb.reactiveCondition), [subBlocks]) - const reactiveCond = reactiveSubBlock?.reactiveCondition - - const canonicalIndex = useMemo(() => buildCanonicalIndex(subBlocks), [subBlocks]) - - // Resolve watchFields through canonical index to get the active credential value - const watchedCredentialId = useSubBlockStore( - useCallback( - (state) => { - if (!reactiveCond || !activeWorkflowId) return '' - const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {} - for (const field of reactiveCond.watchFields) { - const val = resolveDependencyValue( - field, - blockValues, - canonicalIndex, - canonicalModeOverrides - ) - if (val && typeof val === 'string') return val - } - return '' - }, - [reactiveCond, activeWorkflowId, blockId, canonicalIndex, canonicalModeOverrides] - ) - ) - - // Always call useWorkspaceCredential (stable hook count), disable when not needed - const { data: credential } = useWorkspaceCredential( - watchedCredentialId || undefined, - Boolean(reactiveCond && watchedCredentialId) - ) - - return useMemo(() => { - const hidden = new Set() - if (!reactiveSubBlock || !reactiveCond) return hidden - - const conditionMet = credential?.type === reactiveCond.requiredType - if (!conditionMet) { - hidden.add(reactiveSubBlock.id) - } - return hidden - }, [reactiveSubBlock, reactiveCond, credential?.type]) -} - /** * Custom hook for computing subblock layout in the editor panel. * Determines which subblocks should be visible based on mode, conditions, and feature flags. diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index b7b04f38bb2..970b572f4ee 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -47,6 +47,7 @@ import { useReactivateSchedule, useScheduleInfo } from '@/hooks/queries/schedule import { useSkills } from '@/hooks/queries/skills' import { useTablesList } from '@/hooks/queries/tables' import { useWorkflowMap } from '@/hooks/queries/workflows' +import { useReactiveConditions } from '@/hooks/use-reactive-conditions' import { useSelectorDisplayName } from '@/hooks/use-selector-display-name' import { useVariablesStore } from '@/stores/panel' import { useSubBlockStore } from '@/stores/workflows/subblock/store' @@ -942,6 +943,13 @@ export const WorkflowBlock = memo(function WorkflowBlock({ const canonicalIndex = useMemo(() => buildCanonicalIndex(config.subBlocks), [config.subBlocks]) const canonicalModeOverrides = currentStoreBlock?.data?.canonicalModes + const hiddenByReactiveCondition = useReactiveConditions( + config.subBlocks, + id, + activeWorkflowId, + canonicalModeOverrides + ) + const subBlockRowsData = useMemo(() => { const rows: SubBlockConfig[][] = [] let currentRow: SubBlockConfig[] = [] @@ -979,6 +987,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ const visibleSubBlocks = config.subBlocks.filter((block) => { if (block.hidden) return false if (block.hideFromPreview) return false + if (hiddenByReactiveCondition.has(block.id)) return false if (!isSubBlockFeatureEnabled(block)) return false if (isSubBlockHidden(block)) return false @@ -1047,6 +1056,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ canonicalModeOverrides, userPermissions.canEdit, canonicalIndex, + hiddenByReactiveCondition, blockSubBlockValues, activeWorkflowId, ]) diff --git a/apps/sim/hooks/use-reactive-conditions.ts b/apps/sim/hooks/use-reactive-conditions.ts new file mode 100644 index 00000000000..0971eee2cd3 --- /dev/null +++ b/apps/sim/hooks/use-reactive-conditions.ts @@ -0,0 +1,62 @@ +import { useCallback, useMemo } from 'react' +import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility' +import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility' +import type { SubBlockConfig } from '@/blocks/types' +import { useWorkspaceCredential } from '@/hooks/queries/credentials' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' + +/** + * Evaluates reactive conditions for subblocks. Always calls the same hooks + * regardless of whether a reactive condition exists (Rules of Hooks). + * + * Returns a Set of subblock IDs that should be hidden. + */ +export function useReactiveConditions( + subBlocks: SubBlockConfig[], + blockId: string, + activeWorkflowId: string | null, + canonicalModeOverrides?: CanonicalModeOverrides +): Set { + const reactiveSubBlock = useMemo(() => subBlocks.find((sb) => sb.reactiveCondition), [subBlocks]) + const reactiveCond = reactiveSubBlock?.reactiveCondition + + const canonicalIndex = useMemo(() => buildCanonicalIndex(subBlocks), [subBlocks]) + + // Resolve watchFields through canonical index to get the active credential value + const watchedCredentialId = useSubBlockStore( + useCallback( + (state) => { + if (!reactiveCond || !activeWorkflowId) return '' + const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {} + for (const field of reactiveCond.watchFields) { + const val = resolveDependencyValue( + field, + blockValues, + canonicalIndex, + canonicalModeOverrides + ) + if (val && typeof val === 'string') return val + } + return '' + }, + [reactiveCond, activeWorkflowId, blockId, canonicalIndex, canonicalModeOverrides] + ) + ) + + // Always call useWorkspaceCredential (stable hook count), disable when not needed + const { data: credential } = useWorkspaceCredential( + watchedCredentialId || undefined, + Boolean(reactiveCond && watchedCredentialId) + ) + + return useMemo(() => { + const hidden = new Set() + if (!reactiveSubBlock || !reactiveCond) return hidden + + const conditionMet = credential?.type === reactiveCond.requiredType + if (!conditionMet) { + hidden.add(reactiveSubBlock.id) + } + return hidden + }, [reactiveSubBlock, reactiveCond, credential?.type]) +} diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts index b15785f9fd4..aec442ceff6 100644 --- a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts +++ b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts @@ -5,6 +5,7 @@ import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions' import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool' import { GetBlocksMetadataInput, GetBlocksMetadataResult } from '@/lib/copilot/tools/shared/schemas' import { getAllowedIntegrationsFromEnv, isHosted } from '@/lib/core/config/feature-flags' +import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils' import { registry as blockRegistry } from '@/blocks/registry' import { AuthMode, type BlockConfig, isHiddenFromDisplay } from '@/blocks/types' import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check' @@ -342,6 +343,20 @@ function transformBlockMetadata(metadata: CopilotBlockMetadata): any { service: metadata.id, // e.g., 'gmail', 'slack', etc. description: `OAuth authentication required for ${metadata.name}`, } + + // Check if this service also supports service account credentials + const oauthSubBlock = metadata.inputSchema?.find( + (sb: CopilotSubblockMetadata) => sb.type === 'oauth-input' && sb.serviceId + ) + if (oauthSubBlock?.serviceId) { + const serviceAccountProviderId = getServiceAccountProviderForProviderId( + oauthSubBlock.serviceId + ) + if (serviceAccountProviderId) { + transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId + transformed.requiredCredentials.description = `OAuth or service account authentication supported for ${metadata.name}` + } + } } else if (metadata.authType === 'API Key') { transformed.requiredCredentials = { type: 'api_key',