feat(search): add tables, files, knowledge bases, and jobs to cmd-k search (#3800)

* feat(search): add tables, files, knowledge bases, and jobs to cmd-k search

* fix(search): address PR feedback — drop files/jobs, add onSelect to memo

* fix(search): add files back with per-file deep links, keep jobs out

* fix(search): remove onSelect from memo comparator to match existing pattern
This commit is contained in:
Waleed
2026-03-26 19:18:21 -07:00
committed by GitHub
parent 4c474e03c1
commit e70e1ec8c5
5 changed files with 132 additions and 0 deletions

View File

@@ -163,3 +163,27 @@ export const MemoizedPageItem = memo(
prev.name === next.name &&
prev.shortcut === next.shortcut
)
export const MemoizedIconItem = memo(
function IconItem({
value,
onSelect,
name,
icon: Icon,
}: {
value: string
onSelect: () => void
name: string
icon: ComponentType<{ className?: string }>
}) {
return (
<Command.Item value={value} onSelect={onSelect} className={COMMAND_ITEM_CLASSNAME}>
<div className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>
<Icon className='h-[14px] w-[14px] text-[var(--text-icon)]' />
</div>
<span className='truncate font-base text-[var(--text-body)]'>{name}</span>
</Command.Item>
)
},
(prev, next) => prev.value === next.value && prev.name === next.name && prev.icon === next.icon
)

View File

@@ -1,7 +1,9 @@
'use client'
import type { ComponentType } from 'react'
import { memo } from 'react'
import { Command } from 'cmdk'
import { Database, File, Table } from '@/components/emcn/icons'
import type {
SearchBlockItem,
SearchDocItem,
@@ -11,6 +13,7 @@ import type { PageItem, TaskItem, WorkflowItem, WorkspaceItem } from '../utils'
import { GROUP_HEADING_CLASSNAME } from '../utils'
import {
MemoizedCommandItem,
MemoizedIconItem,
MemoizedPageItem,
MemoizedTaskItem,
MemoizedWorkflowItem,
@@ -239,3 +242,36 @@ export const PagesGroup = memo(function PagesGroup({
</Command.Group>
)
})
export const TablesGroup = createIconGroup('Tables', 'table', Table)
export const FilesGroup = createIconGroup('Files', 'file', File)
export const KnowledgeBasesGroup = createIconGroup('Knowledge Bases', 'knowledge-base', Database)
function createIconGroup(
heading: string,
prefix: string,
icon: ComponentType<{ className?: string }>
) {
return memo(function IconGroup({
items,
onSelect,
}: {
items: TaskItem[]
onSelect: (item: TaskItem) => void
}) {
if (items.length === 0) return null
return (
<Command.Group heading={heading} className={GROUP_HEADING_CLASSNAME}>
{items.map((item) => (
<MemoizedIconItem
key={item.id}
value={`${item.name} ${prefix}-${item.id}`}
onSelect={() => onSelect(item)}
name={item.name}
icon={icon}
/>
))}
</Command.Group>
)
})
}

View File

@@ -21,7 +21,10 @@ import type {
import {
BlocksGroup,
DocsGroup,
FilesGroup,
KnowledgeBasesGroup,
PagesGroup,
TablesGroup,
TasksGroup,
ToolOpsGroup,
ToolsGroup,
@@ -40,6 +43,9 @@ export function SearchModal({
workflows = [],
workspaces = [],
tasks = [],
tables = [],
files = [],
knowledgeBases = [],
isOnWorkflowPage = false,
}: SearchModalProps) {
const params = useParams()
@@ -284,6 +290,20 @@ export function SearchModal({
return filterAndSort(docs, (d) => `${d.name} docs documentation doc-${d.id}`, deferredSearch)
}, [isOnWorkflowPage, docs, deferredSearch])
const filteredTables = useMemo(
() => filterAndSort(tables, (t) => `${t.name} table-${t.id}`, deferredSearch),
[tables, deferredSearch]
)
const filteredFiles = useMemo(
() => filterAndSort(files, (f) => `${f.name} file-${f.id}`, deferredSearch),
[files, deferredSearch]
)
const filteredKnowledgeBases = useMemo(
() =>
filterAndSort(knowledgeBases, (kb) => `${kb.name} knowledge-base-${kb.id}`, deferredSearch),
[knowledgeBases, deferredSearch]
)
const filteredWorkflows = useMemo(
() => filterAndSort(workflows, (w) => `${w.name} workflow-${w.id}`, deferredSearch),
[workflows, deferredSearch]
@@ -346,6 +366,9 @@ export function SearchModal({
<TriggersGroup items={filteredTriggers} onSelect={handleBlockSelectAsTrigger} />
<WorkflowsGroup items={filteredWorkflows} onSelect={handleWorkflowSelect} />
<TasksGroup items={filteredTasks} onSelect={handleTaskSelect} />
<TablesGroup items={filteredTables} onSelect={handleTaskSelect} />
<FilesGroup items={filteredFiles} onSelect={handleTaskSelect} />
<KnowledgeBasesGroup items={filteredKnowledgeBases} onSelect={handleTaskSelect} />
<ToolOpsGroup items={filteredToolOps} onSelect={handleToolOperationSelect} />
<WorkspacesGroup items={filteredWorkspaces} onSelect={handleWorkspaceSelect} />
<DocsGroup items={filteredDocs} onSelect={handleDocSelect} />

View File

@@ -37,6 +37,9 @@ export interface SearchModalProps {
workflows?: WorkflowItem[]
workspaces?: WorkspaceItem[]
tasks?: TaskItem[]
tables?: TaskItem[]
files?: TaskItem[]
knowledgeBases?: TaskItem[]
isOnWorkflowPage?: boolean
}

View File

@@ -77,6 +77,8 @@ import {
} from '@/app/workspace/[workspaceId]/w/hooks'
import { getBrandConfig } from '@/ee/whitelabeling'
import { useFolders } from '@/hooks/queries/folders'
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
import { useTablesList } from '@/hooks/queries/tables'
import {
useDeleteTask,
useDeleteTasks,
@@ -85,6 +87,7 @@ import {
useRenameTask,
useTasks,
} from '@/hooks/queries/tasks'
import { useWorkspaceFiles } from '@/hooks/queries/workspace-files'
import { usePermissionConfig } from '@/hooks/use-permission-config'
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
import { useTaskEvents } from '@/hooks/use-task-events'
@@ -745,6 +748,46 @@ export const Sidebar = memo(function Sidebar() {
[fetchedTasks, workspaceId]
)
const { data: fetchedTables = [] } = useTablesList(workspaceId)
const { data: fetchedFiles = [] } = useWorkspaceFiles(workspaceId)
const { data: fetchedKnowledgeBases = [] } = useKnowledgeBasesQuery(workspaceId)
const searchModalTables = useMemo(
() =>
permissionConfig.hideTablesTab
? []
: fetchedTables.map((t) => ({
id: t.id,
name: t.name,
href: `/workspace/${workspaceId}/tables/${t.id}`,
})),
[fetchedTables, workspaceId, permissionConfig.hideTablesTab]
)
const searchModalFiles = useMemo(
() =>
permissionConfig.hideFilesTab
? []
: fetchedFiles.map((f) => ({
id: f.id,
name: f.name,
href: `/workspace/${workspaceId}/files/${f.id}`,
})),
[fetchedFiles, workspaceId, permissionConfig.hideFilesTab]
)
const searchModalKnowledgeBases = useMemo(
() =>
permissionConfig.hideKnowledgeBaseTab
? []
: fetchedKnowledgeBases.map((kb) => ({
id: kb.id,
name: kb.name,
href: `/workspace/${workspaceId}/knowledge/${kb.id}`,
})),
[fetchedKnowledgeBases, workspaceId, permissionConfig.hideKnowledgeBaseTab]
)
const taskIds = useMemo(() => tasks.map((t) => t.id).filter((id) => id !== 'new'), [tasks])
const { selectedTasks, handleTaskClick } = useTaskSelection({ taskIds })
@@ -1671,6 +1714,9 @@ export const Sidebar = memo(function Sidebar() {
workflows={searchModalWorkflows}
workspaces={searchModalWorkspaces}
tasks={tasks}
tables={searchModalTables}
files={searchModalFiles}
knowledgeBases={searchModalKnowledgeBases}
isOnWorkflowPage={!!workflowId}
/>