Skip to content

Commit ffe6806

Browse files
committed
improvement(knowledge): upgrade document list filter to combobox style
1 parent bf0bcf3 commit ffe6806

File tree

2 files changed

+157
-70
lines changed

2 files changed

+157
-70
lines changed

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx

Lines changed: 95 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
Trash,
1616
} from '@/components/emcn'
1717
import { SearchHighlight } from '@/components/ui/search-highlight'
18-
import { cn } from '@/lib/core/utils/cn'
1918
import type { ChunkData } from '@/lib/knowledge/types'
2019
import { formatTokenCount } from '@/lib/tokenization'
2120
import type {
@@ -152,7 +151,14 @@ export function Document({
152151

153152
const [searchQuery, setSearchQuery] = useState('')
154153
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('')
155-
const [enabledFilter, setEnabledFilter] = useState<'all' | 'enabled' | 'disabled'>('all')
154+
const [enabledFilter, setEnabledFilter] = useState<string[]>([])
155+
const [activeSort, setActiveSort] = useState<{
156+
column: string
157+
direction: 'asc' | 'desc'
158+
} | null>(null)
159+
160+
const enabledFilterParam =
161+
enabledFilter.length === 1 ? (enabledFilter[0] as 'enabled' | 'disabled') : 'all'
156162

157163
const {
158164
chunks: initialChunks,
@@ -165,7 +171,7 @@ export function Document({
165171
refreshChunks: initialRefreshChunks,
166172
updateChunk: initialUpdateChunk,
167173
isFetching: isFetchingChunks,
168-
} = useDocumentChunks(knowledgeBaseId, documentId, currentPageFromURL, '', enabledFilter)
174+
} = useDocumentChunks(knowledgeBaseId, documentId, currentPageFromURL, '', enabledFilterParam)
169175

170176
const { data: searchResults = [], error: searchQueryError } = useDocumentChunkSearchQuery(
171177
{
@@ -229,7 +235,28 @@ export function Document({
229235
searchStartIndex + SEARCH_PAGE_SIZE
230236
)
231237

232-
const displayChunks = showingSearch ? paginatedSearchResults : initialChunks
238+
const rawDisplayChunks = showingSearch ? paginatedSearchResults : initialChunks
239+
240+
const displayChunks = useMemo(() => {
241+
if (!activeSort || !rawDisplayChunks) return rawDisplayChunks ?? []
242+
const { column, direction } = activeSort
243+
return [...rawDisplayChunks].sort((a, b) => {
244+
let cmp = 0
245+
switch (column) {
246+
case 'index':
247+
cmp = a.chunkIndex - b.chunkIndex
248+
break
249+
case 'tokens':
250+
cmp = (a.tokenCount ?? 0) - (b.tokenCount ?? 0)
251+
break
252+
case 'status':
253+
cmp = (a.enabled ? 1 : 0) - (b.enabled ? 1 : 0)
254+
break
255+
}
256+
return direction === 'asc' ? cmp : -cmp
257+
})
258+
}, [rawDisplayChunks, activeSort])
259+
233260
const currentPage = showingSearch ? searchCurrentPage : initialPage
234261
const totalPages = showingSearch ? searchTotalPages : initialTotalPages
235262
const hasNextPage = showingSearch ? searchCurrentPage < searchTotalPages : initialHasNextPage
@@ -562,46 +589,62 @@ export function Document({
562589
}
563590
: undefined
564591

592+
const enabledDisplayLabel = useMemo(() => {
593+
if (enabledFilter.length === 0) return 'All'
594+
if (enabledFilter.length === 1) return enabledFilter[0] === 'enabled' ? 'Enabled' : 'Disabled'
595+
return `${enabledFilter.length} selected`
596+
}, [enabledFilter])
597+
565598
const filterContent = (
566-
<div className='w-[200px]'>
567-
<div className='border-[var(--border-1)] border-b px-3 py-2'>
599+
<div className='flex w-[240px] flex-col gap-3 p-3'>
600+
<div className='flex flex-col gap-1.5'>
568601
<span className='font-medium text-[var(--text-secondary)] text-caption'>Status</span>
602+
<Combobox
603+
options={[
604+
{ value: 'enabled', label: 'Enabled' },
605+
{ value: 'disabled', label: 'Disabled' },
606+
]}
607+
multiSelect
608+
multiSelectValues={enabledFilter}
609+
onMultiSelectChange={(values) => {
610+
setEnabledFilter(values)
611+
setSelectedChunks(new Set())
612+
void goToPage(1)
613+
}}
614+
overlayContent={
615+
<span className='truncate text-[var(--text-primary)]'>{enabledDisplayLabel}</span>
616+
}
617+
showAllOption
618+
allOptionLabel='All'
619+
size='sm'
620+
className='h-[32px] w-full rounded-md'
621+
/>
569622
</div>
570-
<div className='flex flex-col gap-0.5 px-3 py-2'>
571-
{(['all', 'enabled', 'disabled'] as const).map((value) => (
572-
<button
573-
key={value}
574-
type='button'
575-
className={cn(
576-
'flex w-full cursor-pointer select-none items-center rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-secondary)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-active)]',
577-
enabledFilter === value && 'bg-[var(--surface-active)]'
578-
)}
579-
onClick={() => {
580-
setEnabledFilter(value)
581-
setSelectedChunks(new Set())
582-
void goToPage(1)
583-
}}
584-
>
585-
{value.charAt(0).toUpperCase() + value.slice(1)}
586-
</button>
587-
))}
588-
</div>
623+
{enabledFilter.length > 0 && (
624+
<button
625+
type='button'
626+
onClick={() => {
627+
setEnabledFilter([])
628+
setSelectedChunks(new Set())
629+
void goToPage(1)
630+
}}
631+
className='flex h-[32px] w-full items-center justify-center rounded-md text-[var(--text-secondary)] text-caption transition-colors hover-hover:bg-[var(--surface-active)]'
632+
>
633+
Clear all filters
634+
</button>
635+
)}
589636
</div>
590637
)
591638

592639
const filterTags: FilterTag[] = [
593-
...(enabledFilter !== 'all'
594-
? [
595-
{
596-
label: `Status: ${enabledFilter === 'enabled' ? 'Enabled' : 'Disabled'}`,
597-
onRemove: () => {
598-
setEnabledFilter('all')
599-
setSelectedChunks(new Set())
600-
void goToPage(1)
601-
},
602-
},
603-
]
604-
: []),
640+
...enabledFilter.map((value) => ({
641+
label: `Status: ${value === 'enabled' ? 'Enabled' : 'Disabled'}`,
642+
onRemove: () => {
643+
setEnabledFilter(enabledFilter.filter((v) => v !== value))
644+
setSelectedChunks(new Set())
645+
void goToPage(1)
646+
},
647+
})),
605648
]
606649

607650
const handleChunkClick = useCallback((rowId: string) => {
@@ -814,6 +857,20 @@ export function Document({
814857
}
815858
: undefined
816859

860+
const sortConfig: SortConfig = useMemo(
861+
() => ({
862+
options: [
863+
{ id: 'index', label: 'Index' },
864+
{ id: 'tokens', label: 'Tokens' },
865+
{ id: 'status', label: 'Status' },
866+
],
867+
active: activeSort,
868+
onSort: (column, direction) => setActiveSort({ column, direction }),
869+
onClear: () => setActiveSort(null),
870+
}),
871+
[activeSort]
872+
)
873+
817874
const chunkRows: ResourceRow[] = useMemo(() => {
818875
if (!isCompleted) {
819876
return [
@@ -1100,6 +1157,7 @@ export function Document({
11001157
emptyMessage={emptyMessage}
11011158
filter={combinedError ? undefined : filterContent}
11021159
filterTags={combinedError ? undefined : filterTags}
1160+
sort={combinedError ? undefined : sortConfig}
11031161
/>
11041162

11051163
<DocumentTagsModal

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export function KnowledgeBase({
208208

209209
const [searchQuery, setSearchQuery] = useState('')
210210
const [showTagsModal, setShowTagsModal] = useState(false)
211-
const [enabledFilter, setEnabledFilter] = useState<'all' | 'enabled' | 'disabled'>('all')
211+
const [enabledFilter, setEnabledFilter] = useState<string[]>([])
212212
const [tagFilterEntries, setTagFilterEntries] = useState<
213213
{
214214
id: string
@@ -235,6 +235,17 @@ export function KnowledgeBase({
235235
[tagFilterEntries]
236236
)
237237

238+
const enabledFilterParam = useMemo<'all' | 'enabled' | 'disabled'>(() => {
239+
if (enabledFilter.length === 1) return enabledFilter[0] as 'enabled' | 'disabled'
240+
return 'all'
241+
}, [enabledFilter])
242+
243+
const enabledDisplayLabel = useMemo(() => {
244+
if (enabledFilter.length === 0) return 'All'
245+
if (enabledFilter.length === 1) return enabledFilter[0] === 'enabled' ? 'Enabled' : 'Disabled'
246+
return '2 selected'
247+
}, [enabledFilter])
248+
238249
const handleSearchChange = useCallback((newQuery: string) => {
239250
setSearchQuery(newQuery)
240251
setCurrentPage(1)
@@ -301,7 +312,7 @@ export function KnowledgeBase({
301312
if (hasSyncingConnectorsRef.current) return 5000
302313
return false
303314
},
304-
enabledFilter,
315+
enabledFilter: enabledFilterParam,
305316
tagFilters: activeTagFilters.length > 0 ? activeTagFilters : undefined,
306317
})
307318

@@ -571,7 +582,7 @@ export function KnowledgeBase({
571582
knowledgeBaseId: id,
572583
operation: 'enable',
573584
selectAll: true,
574-
enabledFilter,
585+
enabledFilter: enabledFilterParam,
575586
},
576587
{
577588
onSuccess: (result) => {
@@ -618,7 +629,7 @@ export function KnowledgeBase({
618629
knowledgeBaseId: id,
619630
operation: 'disable',
620631
selectAll: true,
621-
enabledFilter,
632+
enabledFilter: enabledFilterParam,
622633
},
623634
{
624635
onSuccess: (result) => {
@@ -667,7 +678,7 @@ export function KnowledgeBase({
667678
knowledgeBaseId: id,
668679
operation: 'delete',
669680
selectAll: true,
670-
enabledFilter,
681+
enabledFilter: enabledFilterParam,
671682
},
672683
{
673684
onSuccess: (result) => {
@@ -707,12 +718,12 @@ export function KnowledgeBase({
707718

708719
const selectedDocumentsList = documents.filter((doc) => selectedDocuments.has(doc.id))
709720
const enabledCount = isSelectAllMode
710-
? enabledFilter === 'disabled'
721+
? enabledFilterParam === 'disabled'
711722
? 0
712723
: pagination.total
713724
: selectedDocumentsList.filter((doc) => doc.enabled).length
714725
const disabledCount = isSelectAllMode
715-
? enabledFilter === 'enabled'
726+
? enabledFilterParam === 'enabled'
716727
? 0
717728
: pagination.total
718729
: selectedDocumentsList.filter((doc) => !doc.enabled).length
@@ -813,30 +824,45 @@ export function KnowledgeBase({
813824
}
814825

815826
const filterContent = (
816-
<div className='w-[200px]'>
817-
<div className='border-[var(--border-1)] border-b px-3 py-2'>
827+
<div className='flex w-[240px] flex-col gap-3 p-3'>
828+
<div className='flex flex-col gap-1.5'>
818829
<span className='font-medium text-[var(--text-secondary)] text-caption'>Status</span>
830+
<Combobox
831+
options={[
832+
{ value: 'enabled', label: 'Enabled' },
833+
{ value: 'disabled', label: 'Disabled' },
834+
]}
835+
multiSelect
836+
multiSelectValues={enabledFilter}
837+
onMultiSelectChange={(values) => {
838+
setEnabledFilter(values)
839+
setCurrentPage(1)
840+
setSelectedDocuments(new Set())
841+
setIsSelectAllMode(false)
842+
}}
843+
overlayContent={
844+
<span className='truncate text-[var(--text-primary)]'>{enabledDisplayLabel}</span>
845+
}
846+
showAllOption
847+
allOptionLabel='All'
848+
size='sm'
849+
className='h-[32px] w-full rounded-md'
850+
/>
819851
</div>
820-
<div className='flex flex-col gap-0.5 px-3 py-2'>
821-
{(['all', 'enabled', 'disabled'] as const).map((value) => (
822-
<button
823-
key={value}
824-
type='button'
825-
className={cn(
826-
'flex w-full cursor-pointer select-none items-center rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-secondary)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-active)]',
827-
enabledFilter === value && 'bg-[var(--surface-active)]'
828-
)}
829-
onClick={() => {
830-
setEnabledFilter(value)
831-
setCurrentPage(1)
832-
setSelectedDocuments(new Set())
833-
setIsSelectAllMode(false)
834-
}}
835-
>
836-
{value.charAt(0).toUpperCase() + value.slice(1)}
837-
</button>
838-
))}
839-
</div>
852+
{enabledFilter.length > 0 && (
853+
<button
854+
type='button'
855+
onClick={() => {
856+
setEnabledFilter([])
857+
setCurrentPage(1)
858+
setSelectedDocuments(new Set())
859+
setIsSelectAllMode(false)
860+
}}
861+
className='flex h-[32px] w-full items-center justify-center rounded-md text-[var(--text-secondary)] text-caption transition-colors hover-hover:bg-[var(--surface-active)]'
862+
>
863+
Clear status filter
864+
</button>
865+
)}
840866
<TagFilterSection
841867
tagDefinitions={tagDefinitions}
842868
entries={tagFilterEntries}
@@ -872,12 +898,15 @@ export function KnowledgeBase({
872898
) : null
873899

874900
const filterTags: FilterTag[] = [
875-
...(enabledFilter !== 'all'
901+
...(enabledFilter.length > 0
876902
? [
877903
{
878-
label: `Status: ${enabledFilter === 'enabled' ? 'Enabled' : 'Disabled'}`,
904+
label:
905+
enabledFilter.length === 1
906+
? `Status: ${enabledFilter[0] === 'enabled' ? 'Enabled' : 'Disabled'}`
907+
: 'Status: 2 selected',
879908
onRemove: () => {
880-
setEnabledFilter('all')
909+
setEnabledFilter([])
881910
setCurrentPage(1)
882911
setSelectedDocuments(new Set())
883912
setIsSelectAllMode(false)
@@ -1019,7 +1048,7 @@ export function KnowledgeBase({
10191048

10201049
const emptyMessage = searchQuery
10211050
? 'No documents found'
1022-
: enabledFilter !== 'all' || activeTagFilters.length > 0
1051+
: enabledFilter.length > 0 || activeTagFilters.length > 0
10231052
? 'Nothing matches your filter'
10241053
: undefined
10251054

0 commit comments

Comments
 (0)