import { RepeatIcon, SplitIcon } from 'lucide-react' import { create } from 'zustand' import { devtools } from 'zustand/middleware' import { getToolOperationsIndex } from '@/lib/search/tool-operations' import { getTriggersForSidebar } from '@/lib/workflows/triggers/trigger-utils' import { getAllBlocks } from '@/blocks' import type { BlockConfig, SubBlockConfig } from '@/blocks/types' import type { SearchBlockItem, SearchData, SearchDocItem, SearchModalState, SearchToolOperationItem, } from './types' const initialData: SearchData = { blocks: [], tools: [], triggers: [], toolOperations: [], docs: [], isInitialized: false, } type CommandSearchableOption = { label: string id: string hidden?: boolean } function getCommandSearchableOptions(subBlock: SubBlockConfig): CommandSearchableOption[] { if (!subBlock.options) return [] try { const options = typeof subBlock.options === 'function' ? subBlock.options() : subBlock.options return Array.isArray(options) ? options : [] } catch { return [] } } export function buildCommandSearchableOptionSearchValue(block: BlockConfig): string { const terms = new Set() for (const subBlock of block.subBlocks) { if ( (subBlock.type !== 'dropdown' && subBlock.type !== 'combobox') || !subBlock.commandSearchable ) { continue } for (const option of getCommandSearchableOptions(subBlock)) { if (option.hidden) continue const subBlockTitle = subBlock.title ?? subBlock.id terms.add(subBlockTitle) terms.add(option.label) terms.add(option.id) } } return Array.from(terms).join(' ') } export const useSearchModalStore = create()( devtools( (set, _) => ({ isOpen: false, sections: null, pendingConnect: null, data: initialData, setOpen: (open: boolean) => { set({ isOpen: open, sections: null, pendingConnect: null }) }, open: (options) => { set({ isOpen: true, sections: options?.sections ?? null, pendingConnect: options?.pendingConnect ?? null, }) }, close: () => { set({ isOpen: false, sections: null, pendingConnect: null }) }, initializeData: (filterBlocks) => { const allBlocks = getAllBlocks() const filteredAllBlocks = filterBlocks(allBlocks) as typeof allBlocks const regularBlocks: SearchBlockItem[] = [] const tools: SearchBlockItem[] = [] const docs: SearchDocItem[] = [] for (const block of filteredAllBlocks) { if (block.hideFromToolbar) continue const searchItem: SearchBlockItem = { id: block.type, name: block.name, icon: block.icon, bgColor: block.bgColor || '#6B7280', type: block.type, searchValue: `${block.name} ${block.type} ${buildCommandSearchableOptionSearchValue(block)}`, } if (block.category === 'blocks' && block.type !== 'starter') { regularBlocks.push(searchItem) } else if (block.category === 'tools') { tools.push(searchItem) } if (block.docsLink) { docs.push({ id: `docs-${block.type}`, name: block.name, icon: block.icon, href: block.docsLink, }) } } const specialBlocks: SearchBlockItem[] = [ { id: 'loop', name: 'Loop', icon: RepeatIcon, bgColor: '#2FB3FF', type: 'loop', }, { id: 'parallel', name: 'Parallel', icon: SplitIcon, bgColor: '#FEE12B', type: 'parallel', }, ] const blocks = [...regularBlocks, ...(filterBlocks(specialBlocks) as SearchBlockItem[])] const allTriggers = getTriggersForSidebar() const filteredTriggers = filterBlocks(allTriggers) as typeof allTriggers const priorityOrder = ['Start', 'Schedule', 'Webhook'] const sortedTriggers = [...filteredTriggers].sort( (a: (typeof filteredTriggers)[number], b: (typeof filteredTriggers)[number]) => { const aIndex = priorityOrder.indexOf(a.name) const bIndex = priorityOrder.indexOf(b.name) const aHasPriority = aIndex !== -1 const bHasPriority = bIndex !== -1 if (aHasPriority && bHasPriority) return aIndex - bIndex if (aHasPriority) return -1 if (bHasPriority) return 1 return a.name.localeCompare(b.name) } ) const triggers = sortedTriggers.map( (block): SearchBlockItem => ({ id: block.type, name: block.name, icon: block.icon, bgColor: block.bgColor || '#6B7280', type: block.type, config: block, }) ) const allowedBlockTypes = new Set(tools.map((t) => t.type)) const toolOperations: SearchToolOperationItem[] = getToolOperationsIndex() .filter((op) => allowedBlockTypes.has(op.blockType)) .map((op) => { 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: { blocks, tools, triggers, toolOperations, docs, isInitialized: true, }, }) }, }), { name: 'search-modal-store' } ) )