diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx index 6a875d4a824..696c3fd82c2 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx @@ -8,7 +8,6 @@ import { truncate } from '@sim/utils/string' import { ChevronDown, ChevronUp, FileText, Pencil, Tag } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' import { useQueryStates } from 'nuqs' -import { SearchHighlight } from '@/components/ui/search-highlight' import type { ChunkData } from '@/lib/knowledge/types' import { formatTokenCount } from '@/lib/tokenization' import type { @@ -38,7 +37,7 @@ import { documentParsers, documentUrlKeys, } from '@/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/search-params' -import { ActionBar } from '@/app/workspace/[workspaceId]/knowledge/[id]/components' +import { ActionBar, SearchHighlight } from '@/app/workspace/[workspaceId]/knowledge/[id]/components' import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { useContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks' diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx index 205de1bd764..f5f988ec658 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx @@ -31,7 +31,6 @@ import { AlertCircle, Pencil, Plus, Tag, X } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' import { useQueryState, useQueryStates } from 'nuqs' import { usePostHog } from 'posthog-js/react' -import { SearchHighlight } from '@/components/ui/search-highlight' import { ALL_TAG_SLOTS, type AllTagSlot, getFieldTypeForSlot } from '@/lib/knowledge/constants' import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types' import { type FilterFieldType, getOperatorsForFieldType } from '@/lib/knowledge/filters/types' @@ -59,6 +58,7 @@ import { ConnectorsSection, DocumentContextMenu, RenameDocumentModal, + SearchHighlight, } from '@/app/workspace/[workspaceId]/knowledge/[id]/components' import { addConnectorParam, diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/index.ts b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/index.ts index d26e85dc9e3..12e32ebf736 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/index.ts @@ -6,3 +6,4 @@ export { ConnectorsSection } from './connectors-section' export { DocumentContextMenu } from './document-context-menu' export { EditConnectorModal } from './edit-connector-modal' export { RenameDocumentModal } from './rename-document-modal' +export { SearchHighlight } from './search-highlight' diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/search-highlight/index.ts b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/search-highlight/index.ts new file mode 100644 index 00000000000..1144ed165cd --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/search-highlight/index.ts @@ -0,0 +1 @@ +export { SearchHighlight } from './search-highlight' diff --git a/apps/sim/components/ui/search-highlight.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/search-highlight/search-highlight.tsx similarity index 85% rename from apps/sim/components/ui/search-highlight.tsx rename to apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/search-highlight/search-highlight.tsx index 050ffc87b85..41062a49b00 100644 --- a/apps/sim/components/ui/search-highlight.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/search-highlight/search-highlight.tsx @@ -4,6 +4,11 @@ interface SearchHighlightProps { className?: string } +/** + * Renders `text` with substrings matching any whitespace-separated term of + * `searchQuery` wrapped in the highlight-match colors. Falls back to plain + * text when the query is empty. + */ export function SearchHighlight({ text, searchQuery, className = '' }: SearchHighlightProps) { if (!searchQuery.trim()) { return {text} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/command-items.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/command-items.tsx index 315422c6ce0..39b7f75d312 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/command-items.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/command-items.tsx @@ -6,56 +6,9 @@ import { cn } from '@sim/emcn' import { File, Workflow } from '@sim/emcn/icons' import { Command } from 'cmdk' import type { CommandItemProps } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils' -import { - COMMAND_ITEM_CLASSNAME, - fuzzyMatch, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils' +import { COMMAND_ITEM_CLASSNAME } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils' import { getTileIconColorClass } from '@/blocks/icon-color' -interface Segment { - text: string - hit: boolean -} - -function buildSegments(text: string, positions: readonly number[]): Segment[] { - const hits = new Set(positions) - const segments: Segment[] = [] - for (let i = 0; i < text.length; i++) { - const hit = hits.has(i) - const last = segments[segments.length - 1] - if (last && last.hit === hit) last.text += text[i] - else segments.push({ text: text[i], hit }) - } - return segments -} - -/** - * Renders `text` with the characters that match `query` emphasized. Falls back - * to plain text when there is no query or no positional match against the - * display text (e.g. the row matched on a hidden id rather than its label). - */ -export const HighlightedText = memo( - function HighlightedText({ text, query }: { text: string; query?: string }) { - if (!query) return <>{text} - const { positions } = fuzzyMatch(text, query) - if (positions.length === 0) return <>{text} - return ( - <> - {buildSegments(text, positions).map((segment, index) => - segment.hit ? ( - - {segment.text} - - ) : ( - {segment.text} - ) - )} - - ) - }, - (prev, next) => prev.text === next.text && prev.query === next.query -) - export const MemoizedCommandItem = memo( function CommandItem({ value, @@ -64,7 +17,6 @@ export const MemoizedCommandItem = memo( bgColor, showColoredIcon, label, - query, }: CommandItemProps) { return ( @@ -81,9 +33,7 @@ export const MemoizedCommandItem = memo( )} /> - - - + {label} ) }, @@ -92,8 +42,7 @@ export const MemoizedCommandItem = memo( prev.icon === next.icon && prev.bgColor === next.bgColor && prev.showColoredIcon === next.showColoredIcon && - prev.label === next.label && - prev.query === next.query + prev.label === next.label ) export const MemoizedActionItem = memo( @@ -103,21 +52,17 @@ export const MemoizedActionItem = memo( icon: Icon, name, shortcut, - query, }: { value: string onSelect: () => void icon: ComponentType<{ className?: string }> name: string shortcut?: string - query?: string }) { return ( - - - + {name} {shortcut && ( {shortcut} @@ -130,8 +75,7 @@ export const MemoizedActionItem = memo( prev.value === next.value && prev.icon === next.icon && prev.name === next.name && - prev.shortcut === next.shortcut && - prev.query === next.query + prev.shortcut === next.shortcut ) export const MemoizedWorkflowItem = memo( @@ -141,14 +85,12 @@ export const MemoizedWorkflowItem = memo( name, folderPath, isCurrent, - query, }: { value: string onSelect: () => void name: string folderPath?: string[] isCurrent?: boolean - query?: string }) { return ( @@ -156,9 +98,7 @@ export const MemoizedWorkflowItem = memo( - - - + {name} {isCurrent && (current)} {folderPath && folderPath.length > 0 && ( @@ -181,7 +121,6 @@ export const MemoizedWorkflowItem = memo( prev.value === next.value && prev.name === next.name && prev.isCurrent === next.isCurrent && - prev.query === next.query && (prev.folderPath === next.folderPath || (prev.folderPath?.length === next.folderPath?.length && (prev.folderPath ?? []).every((segment, i) => segment === next.folderPath?.[i]))) @@ -193,13 +132,11 @@ export const MemoizedFileItem = memo( onSelect, name, folderPath, - query, }: { value: string onSelect: () => void name: string folderPath?: string[] - query?: string }) { return ( @@ -207,9 +144,7 @@ export const MemoizedFileItem = memo( - - - + {name} {folderPath && folderPath.length > 0 && ( @@ -230,7 +165,6 @@ export const MemoizedFileItem = memo( (prev, next) => prev.value === next.value && prev.name === next.name && - prev.query === next.query && (prev.folderPath === next.folderPath || (prev.folderPath?.length === next.folderPath?.length && (prev.folderPath ?? []).every((segment, i) => segment === next.folderPath?.[i]))) @@ -241,22 +175,18 @@ export const MemoizedTaskItem = memo( value, onSelect, name, - query, }: { value: string onSelect: () => void name: string - query?: string }) { return ( - - - + {name} ) }, - (prev, next) => prev.value === next.value && prev.name === next.name && prev.query === next.query + (prev, next) => prev.value === next.value && prev.name === next.name ) export const MemoizedWorkspaceItem = memo( @@ -265,30 +195,23 @@ export const MemoizedWorkspaceItem = memo( onSelect, name, isCurrent, - query, }: { value: string onSelect: () => void name: string isCurrent?: boolean - query?: string }) { return ( - - - + {name} {isCurrent && (current)} ) }, (prev, next) => - prev.value === next.value && - prev.name === next.name && - prev.isCurrent === next.isCurrent && - prev.query === next.query + prev.value === next.value && prev.name === next.name && prev.isCurrent === next.isCurrent ) export const MemoizedPageItem = memo( @@ -298,21 +221,17 @@ export const MemoizedPageItem = memo( icon: Icon, name, shortcut, - query, }: { value: string onSelect: () => void icon: ComponentType<{ className?: string }> name: string shortcut?: string - query?: string }) { return ( - - - + {name} {shortcut && ( {shortcut} @@ -325,8 +244,7 @@ export const MemoizedPageItem = memo( prev.value === next.value && prev.icon === next.icon && prev.name === next.name && - prev.shortcut === next.shortcut && - prev.query === next.query + prev.shortcut === next.shortcut ) export const MemoizedIconItem = memo( @@ -335,26 +253,18 @@ export const MemoizedIconItem = memo( onSelect, name, icon: Icon, - query, }: { value: string onSelect: () => void name: string icon: ComponentType<{ className?: string }> - query?: string }) { return ( - - - + {name} ) }, - (prev, next) => - prev.value === next.value && - prev.name === next.name && - prev.icon === next.icon && - prev.query === next.query + (prev, next) => prev.value === next.value && prev.name === next.name && prev.icon === next.icon ) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/index.ts b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/index.ts index 49a29bec4d2..005b06db40f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/index.ts @@ -1,5 +1,4 @@ export { - HighlightedText, MemoizedActionItem, MemoizedCommandItem, MemoizedFileItem, diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups/search-groups.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups/search-groups.tsx index 4a6eaed1524..f8e071be9d0 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups/search-groups.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups/search-groups.tsx @@ -33,11 +33,9 @@ import type { export const ActionsGroup = memo(function ActionsGroup({ items, onSelect, - query, }: { items: ActionItem[] onSelect: (action: ActionItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -50,7 +48,6 @@ export const ActionsGroup = memo(function ActionsGroup({ icon={action.icon} name={action.name} shortcut={action.shortcut} - query={query} /> ))} @@ -60,11 +57,9 @@ export const ActionsGroup = memo(function ActionsGroup({ export const BlocksGroup = memo(function BlocksGroup({ items, onSelect, - query, }: { items: SearchBlockItem[] onSelect: (block: SearchBlockItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -78,7 +73,6 @@ export const BlocksGroup = memo(function BlocksGroup({ bgColor={block.bgColor} showColoredIcon label={block.name} - query={query} /> ))} @@ -88,11 +82,9 @@ export const BlocksGroup = memo(function BlocksGroup({ export const ToolsGroup = memo(function ToolsGroup({ items, onSelect, - query, }: { items: SearchBlockItem[] onSelect: (tool: SearchBlockItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -106,7 +98,6 @@ export const ToolsGroup = memo(function ToolsGroup({ bgColor={tool.bgColor} showColoredIcon label={tool.name} - query={query} /> ))} @@ -116,11 +107,9 @@ export const ToolsGroup = memo(function ToolsGroup({ export const TriggersGroup = memo(function TriggersGroup({ items, onSelect, - query, }: { items: SearchBlockItem[] onSelect: (trigger: SearchBlockItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -134,7 +123,6 @@ export const TriggersGroup = memo(function TriggersGroup({ bgColor={trigger.bgColor} showColoredIcon label={trigger.name} - query={query} /> ))} @@ -144,11 +132,9 @@ export const TriggersGroup = memo(function TriggersGroup({ export const ToolOpsGroup = memo(function ToolOpsGroup({ items, onSelect, - query, }: { items: SearchToolOperationItem[] onSelect: (op: SearchToolOperationItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -162,7 +148,6 @@ export const ToolOpsGroup = memo(function ToolOpsGroup({ bgColor={op.bgColor} showColoredIcon label={op.name} - query={query} /> ))} @@ -172,11 +157,9 @@ export const ToolOpsGroup = memo(function ToolOpsGroup({ export const DocsGroup = memo(function DocsGroup({ items, onSelect, - query, }: { items: SearchDocItem[] onSelect: (doc: SearchDocItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -190,7 +173,6 @@ export const DocsGroup = memo(function DocsGroup({ bgColor='#6B7280' showColoredIcon label={doc.name} - query={query} /> ))} @@ -200,11 +182,9 @@ export const DocsGroup = memo(function DocsGroup({ export const WorkflowsGroup = memo(function WorkflowsGroup({ items, onSelect, - query, }: { items: WorkflowItem[] onSelect: (workflow: WorkflowItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -217,7 +197,6 @@ export const WorkflowsGroup = memo(function WorkflowsGroup({ name={workflow.name} folderPath={workflow.folderPath} isCurrent={workflow.isCurrent} - query={query} /> ))} @@ -227,11 +206,9 @@ export const WorkflowsGroup = memo(function WorkflowsGroup({ export const ChatsGroup = memo(function ChatsGroup({ items, onSelect, - query, }: { items: TaskItem[] onSelect: (task: TaskItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -242,7 +219,6 @@ export const ChatsGroup = memo(function ChatsGroup({ value={`${task.name} task-${task.id}`} onSelect={() => onSelect(task)} name={task.name} - query={query} /> ))} @@ -252,11 +228,9 @@ export const ChatsGroup = memo(function ChatsGroup({ export const WorkspacesGroup = memo(function WorkspacesGroup({ items, onSelect, - query, }: { items: WorkspaceItem[] onSelect: (workspace: WorkspaceItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -268,7 +242,6 @@ export const WorkspacesGroup = memo(function WorkspacesGroup({ onSelect={() => onSelect(workspace)} name={workspace.name} isCurrent={workspace.isCurrent} - query={query} /> ))} @@ -278,11 +251,9 @@ export const WorkspacesGroup = memo(function WorkspacesGroup({ export const PagesGroup = memo(function PagesGroup({ items, onSelect, - query, }: { items: PageItem[] onSelect: (page: PageItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -295,7 +266,6 @@ export const PagesGroup = memo(function PagesGroup({ icon={page.icon} name={page.name} shortcut={page.shortcut} - query={query} /> ))} @@ -311,11 +281,9 @@ export const IntegrationsGroup = createColoredIconGroup('Integrations', 'integra export const FilesGroup = memo(function FilesGroup({ items, onSelect, - query, }: { items: FileItem[] onSelect: (file: FileItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -327,7 +295,6 @@ export const FilesGroup = memo(function FilesGroup({ onSelect={() => onSelect(file)} name={file.name} folderPath={file.folderPath} - query={query} /> ))} @@ -344,11 +311,9 @@ function createColoredIconGroup(heading: string, prefix: string) { return memo(function ColoredIconGroup({ items, onSelect, - query, }: { items: IntegrationSearchItem[] onSelect: (item: IntegrationSearchItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -362,7 +327,6 @@ function createColoredIconGroup(heading: string, prefix: string) { bgColor={item.bgColor} showColoredIcon label={item.name} - query={query} /> ))} @@ -378,11 +342,9 @@ function createIconGroup( return memo(function IconGroup({ items, onSelect, - query, }: { items: TaskItem[] onSelect: (item: TaskItem) => void - query?: string }) { if (items.length === 0) return null return ( @@ -394,7 +356,6 @@ function createIconGroup( onSelect={() => onSelect(item)} name={item.name} icon={icon} - query={query} /> ))} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx index ec1dc3e630f..6968fbc91f9 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx @@ -712,105 +712,53 @@ export function SearchModal({ {showSection('actions') && ( - + )} {showSection('connectedAccounts') && ( )} {showSection('integrations') && ( )} {showSection('blocks') && ( - + )} {showSection('tools') && ( - + )} {showSection('triggers') && ( - + )} {showSection('chats') && ( - + )} {showSection('workflows') && ( - + )} {showSection('tables') && ( - + )} {showSection('files') && ( - + )} {showSection('knowledgeBases') && ( - + )} {showSection('toolOperations') && ( - + )} {showSection('workspaces') && ( - - )} - {showSection('docs') && ( - + )} + {showSection('docs') && } {showSection('pages') && ( - + )} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils.ts b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils.ts index a9f3fe9dbbe..5b046c31a52 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils.ts @@ -91,10 +91,8 @@ export interface CommandItemProps { icon: ComponentType<{ className?: string }> bgColor: string showColoredIcon?: boolean - /** Primary text. Matched characters are highlighted against {@link query}. */ + /** Primary text of the row. */ label: string - /** Active search query, used to bold matched characters. */ - query?: string } export const GROUP_HEADING_CLASSNAME = @@ -181,9 +179,8 @@ function tokenFallback(lowerText: string, lowerQuery: string): FuzzyResult { * (`message slack` matches "Slack Send Message") which a strict left-to-right * subsequence would miss. * - * Contiguous substring matches report the indices of the substring itself, so - * highlighting always bolds the run the user actually matched rather than an - * earlier scattered occurrence of the same characters. + * Contiguous substring matches report the indices of the substring itself + * rather than an earlier scattered occurrence of the same characters. */ export function fuzzyMatch(text: string, query: string): FuzzyResult { if (!query) return { matched: true, score: 1, positions: [] } diff --git a/apps/sim/components/ui/index.ts b/apps/sim/components/ui/index.ts index 40ccab62097..234f6f50a60 100644 --- a/apps/sim/components/ui/index.ts +++ b/apps/sim/components/ui/index.ts @@ -1,7 +1,6 @@ export { Button, buttonVariants } from './button' export { GeneratedPasswordInput } from './generated-password-input' export { Progress } from './progress' -export { SearchHighlight } from './search-highlight' export { Select, SelectContent,