Skip to content
Merged
Binary file modified apps/docs/public/static/introduction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { Database, HelpCircle, Layout, Settings } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
import { createPortal } from 'react-dom'
import { Library } from '@/components/emcn'
import { useBrandConfig } from '@/lib/branding/branding'
import { cn } from '@/lib/core/utils/cn'
import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils'
import { SIDEBAR_SCROLL_EVENT } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
import { usePermissionConfig } from '@/hooks/use-permission-config'
import { useSearchModalStore } from '@/stores/modals/search/store'
import type {
SearchBlockItem,
Expand All @@ -18,6 +18,23 @@ import type {
} from '@/stores/modals/search/types'
import { useSettingsModalStore } from '@/stores/modals/settings/store'

function customFilter(value: string, search: string): number {
const searchLower = search.toLowerCase()
const valueLower = value.toLowerCase()

if (valueLower === searchLower) return 1
if (valueLower.startsWith(searchLower)) return 0.9
if (valueLower.includes(searchLower)) return 0.7

const searchWords = searchLower.split(/\s+/).filter(Boolean)
if (searchWords.length > 1) {
const allWordsMatch = searchWords.every((word) => valueLower.includes(word))
if (allWordsMatch) return 0.5
}

return 0
}
Comment thread
waleedlatif1 marked this conversation as resolved.
Comment thread
waleedlatif1 marked this conversation as resolved.
Comment thread
waleedlatif1 marked this conversation as resolved.

interface SearchModalProps {
open: boolean
onOpenChange: (open: boolean) => void
Expand Down Expand Up @@ -48,6 +65,7 @@ interface PageItem {
href?: string
onClick?: () => void
shortcut?: string
hidden?: boolean
}

export function SearchModal({
Expand All @@ -60,11 +78,10 @@ export function SearchModal({
const params = useParams()
const router = useRouter()
const workspaceId = params.workspaceId as string
const brand = useBrandConfig()
const inputRef = useRef<HTMLInputElement>(null)
const [search, setSearch] = useState('')
const [mounted, setMounted] = useState(false)
const openSettingsModal = useSettingsModalStore((state) => state.openModal)
const { config: permissionConfig } = usePermissionConfig()

useEffect(() => {
setMounted(true)
Expand All @@ -79,54 +96,66 @@ export function SearchModal({
}, [])

const pages = useMemo(
(): PageItem[] => [
{
id: 'logs',
name: 'Logs',
icon: Library,
href: `/workspace/${workspaceId}/logs`,
shortcut: '⌘⇧L',
},
{
id: 'templates',
name: 'Templates',
icon: Layout,
href: `/workspace/${workspaceId}/templates`,
},
{
id: 'knowledge-base',
name: 'Knowledge Base',
icon: Database,
href: `/workspace/${workspaceId}/knowledge`,
},
{
id: 'help',
name: 'Help',
icon: HelpCircle,
onClick: openHelpModal,
},
{
id: 'settings',
name: 'Settings',
icon: Settings,
onClick: openSettingsModal,
shortcut: '⌘,',
},
],
[workspaceId, openHelpModal, openSettingsModal]
(): PageItem[] =>
[
{
id: 'logs',
name: 'Logs',
icon: Library,
href: `/workspace/${workspaceId}/logs`,
shortcut: '⌘⇧L',
},
{
id: 'templates',
name: 'Templates',
icon: Layout,
href: `/workspace/${workspaceId}/templates`,
hidden: permissionConfig.hideTemplates,
},
{
id: 'knowledge-base',
name: 'Knowledge Base',
icon: Database,
href: `/workspace/${workspaceId}/knowledge`,
hidden: permissionConfig.hideKnowledgeBaseTab,
},
{
id: 'help',
name: 'Help',
icon: HelpCircle,
onClick: openHelpModal,
},
{
id: 'settings',
name: 'Settings',
icon: Settings,
onClick: openSettingsModal,
},
].filter((page) => !page.hidden),
[
workspaceId,
openHelpModal,
openSettingsModal,
permissionConfig.hideTemplates,
permissionConfig.hideKnowledgeBaseTab,
]
)

useEffect(() => {
if (open) {
setSearch('')
requestAnimationFrame(() => {
inputRef.current?.focus()
})
if (open && inputRef.current) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value'
)?.set
if (nativeInputValueSetter) {
nativeInputValueSetter.call(inputRef.current, '')
inputRef.current.dispatchEvent(new Event('input', { bubbles: true }))
}
inputRef.current.focus()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overly complex DOM manipulation to clear input

Low Severity

The new approach uses Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set to clear the search input, which is more complex than the previous controlled React state pattern (setSearch('')). This DOM manipulation technique relies on browser implementation details and is harder to understand and maintain than standard React state management.

Fix in Cursor Fix in Web

}
}, [open])

const handleSearchChange = useCallback((value: string) => {
setSearch(value)
const handleSearchChange = useCallback(() => {
requestAnimationFrame(() => {
const list = document.querySelector('[cmdk-list]')
if (list) {
Expand Down Expand Up @@ -228,28 +257,6 @@ export function SearchModal({
const showToolOperations = isOnWorkflowPage && toolOperations.length > 0
const showDocs = isOnWorkflowPage && docs.length > 0

const customFilter = useCallback((value: string, search: string, keywords?: string[]) => {
const searchLower = search.toLowerCase()
const valueLower = value.toLowerCase()

if (valueLower === searchLower) return 1
if (valueLower.startsWith(searchLower)) return 0.8
if (valueLower.includes(searchLower)) return 0.6

const searchWords = searchLower.split(/\s+/).filter(Boolean)
const allWordsMatch = searchWords.every((word) => valueLower.includes(word))
if (allWordsMatch && searchWords.length > 0) return 0.4

if (keywords?.length) {
const keywordsLower = keywords.join(' ').toLowerCase()
if (keywordsLower.includes(searchLower)) return 0.3
const keywordWordsMatch = searchWords.every((word) => keywordsLower.includes(word))
if (keywordWordsMatch && searchWords.length > 0) return 0.2
}

return 0
}, [])

if (!mounted) return null

return createPortal(
Expand Down Expand Up @@ -278,7 +285,6 @@ export function SearchModal({
<Command label='Search' filter={customFilter}>
<Command.Input
ref={inputRef}
value={search}
autoFocus
onValueChange={handleSearchChange}
placeholder='Search anything...'
Expand All @@ -295,7 +301,6 @@ export function SearchModal({
<CommandItem
key={block.id}
value={`${block.name} block-${block.id}`}
keywords={[block.description]}
onSelect={() => handleBlockSelect(block, 'block')}
icon={block.icon}
bgColor={block.bgColor}
Expand All @@ -313,7 +318,6 @@ export function SearchModal({
<CommandItem
key={tool.id}
value={`${tool.name} tool-${tool.id}`}
keywords={[tool.description]}
onSelect={() => handleBlockSelect(tool, 'tool')}
icon={tool.icon}
bgColor={tool.bgColor}
Expand All @@ -331,7 +335,6 @@ export function SearchModal({
<CommandItem
key={trigger.id}
value={`${trigger.name} trigger-${trigger.id}`}
keywords={[trigger.description]}
onSelect={() => handleBlockSelect(trigger, 'trigger')}
icon={trigger.icon}
bgColor={trigger.bgColor}
Expand Down Expand Up @@ -371,7 +374,6 @@ export function SearchModal({
<CommandItem
key={op.id}
value={`${op.searchValue} operation-${op.id}`}
keywords={op.keywords}
onSelect={() => handleToolOperationSelect(op)}
icon={op.icon}
bgColor={op.bgColor}
Expand Down Expand Up @@ -458,7 +460,6 @@ const groupHeadingClassName =

interface CommandItemProps {
value: string
keywords?: string[]
onSelect: () => void
icon: React.ComponentType<{ className?: string }>
bgColor: string
Expand All @@ -468,7 +469,6 @@ interface CommandItemProps {

function CommandItem({
value,
keywords,
onSelect,
icon: Icon,
bgColor,
Expand All @@ -478,7 +478,6 @@ function CommandItem({
return (
<Command.Item
value={value}
keywords={keywords}
onSelect={onSelect}
className='group flex h-[28px] w-full cursor-pointer items-center gap-[8px] rounded-[6px] px-[10px] text-left text-[15px] aria-selected:bg-[var(--border)] aria-selected:shadow-sm data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50'
>
Expand Down
10 changes: 10 additions & 0 deletions apps/sim/blocks/blocks/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ Example:
generationType: 'json-object',
},
},
{
id: 'timeout',
title: 'Timeout (ms)',
type: 'short-input',
placeholder: '300000',
description:
'Request timeout in milliseconds (default: 300000 = 5 minutes, max: 600000 = 10 minutes)',
mode: 'advanced',
},
],
tools: {
access: ['http_request'],
Expand All @@ -90,6 +99,7 @@ Example:
headers: { type: 'json', description: 'Request headers' },
body: { type: 'json', description: 'Request body data' },
params: { type: 'json', description: 'URL query parameters' },
timeout: { type: 'number', description: 'Request timeout in milliseconds' },
},
outputs: {
data: { type: 'json', description: 'API response data (JSON, text, or other formats)' },
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/core/security/input-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ export async function secureFetchWithPinnedIP(
method: options.method || 'GET',
headers: sanitizedHeaders,
agent,
timeout: options.timeout || 30000,
timeout: options.timeout || 300000, // Default 5 minutes
Comment thread
waleedlatif1 marked this conversation as resolved.
}

const protocol = isHttps ? https : http
Expand Down
27 changes: 13 additions & 14 deletions apps/sim/stores/modals/search/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export const useSearchModalStore = create<SearchModalState>()(
const searchItem: SearchBlockItem = {
id: block.type,
name: block.name,
description: block.description || '',
icon: block.icon,
bgColor: block.bgColor || '#6B7280',
type: block.type,
Expand All @@ -79,15 +78,13 @@ export const useSearchModalStore = create<SearchModalState>()(
{
id: 'loop',
name: 'Loop',
description: 'Create a Loop',
icon: RepeatIcon,
bgColor: '#2FB3FF',
type: 'loop',
},
{
id: 'parallel',
name: 'Parallel',
description: 'Parallel Execution',
icon: SplitIcon,
bgColor: '#FEE12B',
type: 'parallel',
Expand Down Expand Up @@ -116,7 +113,6 @@ export const useSearchModalStore = create<SearchModalState>()(
(block): SearchBlockItem => ({
id: block.type,
name: block.name,
description: block.description || '',
icon: block.icon,
bgColor: block.bgColor || '#6B7280',
type: block.type,
Expand All @@ -127,16 +123,19 @@ export const useSearchModalStore = create<SearchModalState>()(
const allowedBlockTypes = new Set(tools.map((t) => t.type))
const toolOperations: SearchToolOperationItem[] = getToolOperationsIndex()
.filter((op) => allowedBlockTypes.has(op.blockType))
.map((op) => ({
id: op.id,
name: op.operationName,
searchValue: `${op.serviceName} ${op.operationName}`,
icon: op.icon,
bgColor: op.bgColor,
blockType: op.blockType,
operationId: op.operationId,
keywords: op.aliases,
Comment thread
waleedlatif1 marked this conversation as resolved.
}))
.map((op) => {
// Include aliases in searchValue so synonym search works (e.g., "post" finds "Send Message")
const aliasesStr = op.aliases?.length ? ` ${op.aliases.join(' ')}` : ''
return {
id: op.id,
name: op.operationName,
searchValue: `${op.serviceName} ${op.operationName}${aliasesStr}`,
icon: op.icon,
bgColor: op.bgColor,
blockType: op.blockType,
operationId: op.operationId,
}
})

set({
data: {
Expand Down
2 changes: 0 additions & 2 deletions apps/sim/stores/modals/search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type { BlockConfig } from '@/blocks/types'
export interface SearchBlockItem {
id: string
name: string
description: string
icon: ComponentType<{ className?: string }>
bgColor: string
type: string
Expand All @@ -25,7 +24,6 @@ export interface SearchToolOperationItem {
bgColor: string
blockType: string
operationId: string
keywords: string[]
}

/**
Expand Down
5 changes: 5 additions & 0 deletions apps/sim/tools/http/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
visibility: 'user-or-llm',
description: 'Form data to send (will set appropriate Content-Type)',
},
timeout: {
type: 'number',
visibility: 'user-only',
description: 'Request timeout in milliseconds (default: 300000 = 5 minutes)',
},
},

request: {
Expand Down
1 change: 1 addition & 0 deletions apps/sim/tools/http/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface RequestParams {
params?: TableRow[]
pathParams?: Record<string, string>
formData?: Record<string, string | Blob>
timeout?: number
}

export interface RequestResponse extends ToolResponse {
Expand Down
Loading