diff --git a/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx b/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx index 0b05aa9c26..3f8064d4d2 100644 --- a/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx +++ b/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useReactFlow } from 'reactflow' import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown' import { formatDisplayText } from '@/components/ui/formatted-text' @@ -45,6 +45,23 @@ export function ShortInput({ // Use either controlled or uncontrolled value const value = propValue !== undefined ? propValue : storeValue + // Check if this input is API key related + const isApiKeyField = useMemo(() => { + const normalizedId = config?.id?.replace(/\s+/g, '').toLowerCase() || '' + const normalizedTitle = config?.title?.replace(/\s+/g, '').toLowerCase() || '' + + // Check for common API key naming patterns + const apiKeyPatterns = ['apikey', 'api_key', 'api-key', 'secretkey', 'secret_key', 'secret-key'] + + return apiKeyPatterns.some( + (pattern) => + normalizedId === pattern || + normalizedTitle === pattern || + normalizedId.includes(pattern) || + normalizedTitle.includes(pattern) + ) + }, [config?.id, config?.title]) + // Handle input changes const handleChange = (e: React.ChangeEvent) => { const newValue = e.target.value @@ -60,8 +77,20 @@ export function ShortInput({ // Check for environment variables trigger const envVarTrigger = checkEnvVarTrigger(newValue, newCursorPosition) - setShowEnvVars(envVarTrigger.show) - setSearchTerm(envVarTrigger.show ? envVarTrigger.searchTerm : '') + + // For API key fields, always show dropdown when typing (without requiring {{ trigger) + if (isApiKeyField && isFocused) { + // Only show dropdown if there's text to filter by or the field is empty + const shouldShowDropdown = newValue.trim() !== '' || newValue === '' + setShowEnvVars(shouldShowDropdown) + // Use the entire input value as search term for API key fields, + // but if {{ is detected, use the standard search term extraction + setSearchTerm(envVarTrigger.show ? envVarTrigger.searchTerm : newValue) + } else { + // Normal behavior for non-API key fields + setShowEnvVars(envVarTrigger.show) + setSearchTerm(envVarTrigger.show ? envVarTrigger.searchTerm : '') + } // Check for tag trigger const tagTrigger = checkTagTrigger(newValue, newCursorPosition) @@ -190,6 +219,17 @@ export function ShortInput({ if (e.key === 'Escape') { setShowEnvVars(false) setShowTags(false) + return + } + + // For API key fields, show env vars when clearing with keyboard shortcuts + if ( + isApiKeyField && + (e.key === 'Delete' || e.key === 'Backspace') && + inputRef.current?.selectionStart === 0 && + inputRef.current?.selectionEnd === value?.toString().length + ) { + setTimeout(() => setShowEnvVars(true), 0) } } @@ -222,11 +262,25 @@ export function ShortInput({ onChange={handleChange} onFocus={() => { setIsFocused(true) + + // If this is an API key field, automatically show env vars dropdown + if (isApiKeyField) { + setShowEnvVars(true) + setSearchTerm('') + + // Set cursor position to the end of the input + const inputLength = value?.toString().length ?? 0 + setCursorPosition(inputLength) + } else { + setShowEnvVars(false) + setShowTags(false) + setSearchTerm('') + } + }} + onBlur={() => { + setIsFocused(false) setShowEnvVars(false) - setShowTags(false) - setSearchTerm('') }} - onBlur={() => setIsFocused(false)} onDrop={handleDrop} onDragOver={handleDragOver} onScroll={handleScroll} diff --git a/components/ui/env-var-dropdown.tsx b/components/ui/env-var-dropdown.tsx index 8d3dbc3976..5003ca973d 100644 --- a/components/ui/env-var-dropdown.tsx +++ b/components/ui/env-var-dropdown.tsx @@ -44,18 +44,32 @@ export const EnvVarDropdown: React.FC = ({ // Find the start of the env var syntax (last '{{' before cursor) const lastOpenBraces = textBeforeCursor.lastIndexOf('{{') - // Get the text before the env var syntax - const startText = - lastOpenBraces !== -1 ? textBeforeCursor.slice(0, lastOpenBraces) : textBeforeCursor + // Check if we're in a standard env var context (with braces) or direct typing mode + const isStandardEnvVarContext = lastOpenBraces !== -1 - // Find the end of any existing env var syntax after cursor - const closeIndex = textAfterCursor.indexOf('}}') - const endText = closeIndex !== -1 ? textAfterCursor.slice(closeIndex + 2) : textAfterCursor + if (isStandardEnvVarContext) { + // Standard behavior with {{ }} syntax + const startText = textBeforeCursor.slice(0, lastOpenBraces) - // Construct the new value with proper env var syntax - const newValue = startText + '{{' + envVar + '}}' + endText + // Find the end of any existing env var syntax after cursor + const closeIndex = textAfterCursor.indexOf('}}') + const endText = closeIndex !== -1 ? textAfterCursor.slice(closeIndex + 2) : textAfterCursor + + // Construct the new value with proper env var syntax + const newValue = startText + '{{' + envVar + '}}' + endText + onSelect(newValue) + } else { + // For direct typing mode (API key fields), check if we need to replace existing text + // This handles the case where user has already typed part of a variable name + if (inputValue.trim() !== '') { + // Replace the entire input with the selected env var + onSelect('{{' + envVar + '}}') + } else { + // Empty input, just insert the env var + onSelect('{{' + envVar + '}}') + } + } - onSelect(newValue) onClose?.() } @@ -141,10 +155,17 @@ export const checkEnvVarTrigger = ( ): { show: boolean; searchTerm: string } => { if (cursorPosition >= 2) { const textBeforeCursor = text.slice(0, cursorPosition) + // Look for {{ pattern followed by optional text const match = textBeforeCursor.match(/\{\{(\w*)$/) if (match) { return { show: true, searchTerm: match[1] } } + + // Also check for exact {{ without any text after it + // This ensures all env vars show when user just types {{ + if (textBeforeCursor.endsWith('{{')) { + return { show: true, searchTerm: '' } + } } return { show: false, searchTerm: '' } }