@@ -107,6 +107,8 @@ export function Knowledge() {
107107 direction : 'asc' | 'desc'
108108 } | null > ( null )
109109 const [ connectorFilter , setConnectorFilter ] = useState < 'all' | 'connected' | 'unconnected' > ( 'all' )
110+ const [ contentFilter , setContentFilter ] = useState < 'all' | 'has-docs' | 'empty' > ( 'all' )
111+ const [ ownerFilter , setOwnerFilter ] = useState < string [ ] > ( [ ] )
110112
111113 const [ searchInputValue , setSearchInputValue ] = useState ( '' )
112114 const debouncedSearchQuery = useDebounce ( searchInputValue , 300 )
@@ -192,6 +194,18 @@ export function Knowledge() {
192194 )
193195 }
194196
197+ if ( contentFilter !== 'all' ) {
198+ result = result . filter ( ( kb ) =>
199+ contentFilter === 'has-docs'
200+ ? ( ( kb as KnowledgeBaseWithDocCount ) . docCount ?? 0 ) > 0
201+ : ( ( kb as KnowledgeBaseWithDocCount ) . docCount ?? 0 ) === 0
202+ )
203+ }
204+
205+ if ( ownerFilter . length > 0 ) {
206+ result = result . filter ( ( kb ) => ownerFilter . includes ( kb . userId ) )
207+ }
208+
195209 const col = activeSort ?. column ?? 'created'
196210 const dir = activeSort ?. direction ?? 'desc'
197211 return [ ...result ] . sort ( ( a , b ) => {
@@ -217,7 +231,14 @@ export function Knowledge() {
217231 }
218232 return dir === 'asc' ? cmp : - cmp
219233 } )
220- } , [ knowledgeBases , debouncedSearchQuery , connectorFilter , activeSort ] )
234+ } , [
235+ knowledgeBases ,
236+ debouncedSearchQuery ,
237+ connectorFilter ,
238+ contentFilter ,
239+ ownerFilter ,
240+ activeSort ,
241+ ] )
221242
222243 const rows : ResourceRow [ ] = useMemo (
223244 ( ) =>
@@ -375,21 +396,106 @@ export function Knowledge() {
375396 </ button >
376397 ) ) }
377398 </ div >
399+ < div className = 'border-[var(--border-1)] border-t border-b px-3 py-2' >
400+ < span className = 'font-medium text-[var(--text-secondary)] text-caption' > Content</ span >
401+ </ div >
402+ < div className = 'flex flex-col gap-0.5 px-3 py-2' >
403+ { (
404+ [
405+ { value : 'all' , label : 'All' } ,
406+ { value : 'has-docs' , label : 'Has documents' } ,
407+ { value : 'empty' , label : 'Empty' } ,
408+ ] as const
409+ ) . map ( ( { value, label } ) => (
410+ < button
411+ key = { value }
412+ type = 'button'
413+ className = { cn (
414+ '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)]' ,
415+ contentFilter === value && 'bg-[var(--surface-active)]'
416+ ) }
417+ onClick = { ( ) => setContentFilter ( value ) }
418+ >
419+ { label }
420+ </ button >
421+ ) ) }
422+ </ div >
423+ { members && members . length > 0 && (
424+ < >
425+ < div className = 'border-[var(--border-1)] border-t border-b px-3 py-2' >
426+ < span className = 'font-medium text-[var(--text-secondary)] text-caption' > Owner</ span >
427+ </ div >
428+ < div className = 'flex flex-col gap-0.5 px-3 py-2' >
429+ < button
430+ type = 'button'
431+ className = { cn (
432+ '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)]' ,
433+ ownerFilter . length === 0 && 'bg-[var(--surface-active)]'
434+ ) }
435+ onClick = { ( ) => setOwnerFilter ( [ ] ) }
436+ >
437+ All
438+ </ button >
439+ { members . map ( ( member ) => (
440+ < button
441+ key = { member . userId }
442+ type = 'button'
443+ className = { cn (
444+ 'flex w-full cursor-pointer select-none items-center gap-1.5 rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-secondary)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-active)]' ,
445+ ownerFilter . includes ( member . userId ) && 'bg-[var(--surface-active)]'
446+ ) }
447+ onClick = { ( ) =>
448+ setOwnerFilter ( ( prev ) =>
449+ prev . includes ( member . userId )
450+ ? prev . filter ( ( id ) => id !== member . userId )
451+ : [ ...prev , member . userId ]
452+ )
453+ }
454+ >
455+ { member . image ? (
456+ < img
457+ src = { member . image }
458+ alt = { member . name }
459+ referrerPolicy = 'no-referrer'
460+ className = 'h-[14px] w-[14px] shrink-0 rounded-full border border-[var(--border)] object-cover'
461+ />
462+ ) : (
463+ < span className = 'flex h-[14px] w-[14px] shrink-0 items-center justify-center rounded-full border border-[var(--border)] bg-[var(--surface-3)] font-medium text-[8px] text-[var(--text-secondary)]' >
464+ { member . name . charAt ( 0 ) . toUpperCase ( ) }
465+ </ span >
466+ ) }
467+ < span className = 'truncate' > { member . name } </ span >
468+ </ button >
469+ ) ) }
470+ </ div >
471+ </ >
472+ ) }
378473 </ div >
379474 )
380475
381- const filterTags : FilterTag [ ] = useMemo (
382- ( ) =>
383- connectorFilter === 'all'
384- ? [ ]
385- : [
386- {
387- label : connectorFilter === 'connected' ? 'Connectors: Active' : 'Connectors: None' ,
388- onRemove : ( ) => setConnectorFilter ( 'all' ) ,
389- } ,
390- ] ,
391- [ connectorFilter ]
392- )
476+ const filterTags : FilterTag [ ] = useMemo ( ( ) => {
477+ const tags : FilterTag [ ] = [ ]
478+ if ( connectorFilter !== 'all' ) {
479+ tags . push ( {
480+ label : connectorFilter === 'connected' ? 'Connectors: Active' : 'Connectors: None' ,
481+ onRemove : ( ) => setConnectorFilter ( 'all' ) ,
482+ } )
483+ }
484+ if ( contentFilter !== 'all' ) {
485+ tags . push ( {
486+ label : contentFilter === 'has-docs' ? 'Content: Has documents' : 'Content: Empty' ,
487+ onRemove : ( ) => setContentFilter ( 'all' ) ,
488+ } )
489+ }
490+ if ( ownerFilter . length > 0 ) {
491+ const label =
492+ ownerFilter . length === 1
493+ ? `Owner: ${ members ?. find ( ( m ) => m . userId === ownerFilter [ 0 ] ) ?. name ?? '1 member' } `
494+ : `Owner: ${ ownerFilter . length } members`
495+ tags . push ( { label, onRemove : ( ) => setOwnerFilter ( [ ] ) } )
496+ }
497+ return tags
498+ } , [ connectorFilter , contentFilter , ownerFilter , members ] )
393499
394500 return (
395501 < >
0 commit comments