@@ -15,7 +15,6 @@ import {
1515 Trash ,
1616} from '@/components/emcn'
1717import { SearchHighlight } from '@/components/ui/search-highlight'
18- import { cn } from '@/lib/core/utils/cn'
1918import type { ChunkData } from '@/lib/knowledge/types'
2019import { formatTokenCount } from '@/lib/tokenization'
2120import 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
0 commit comments