@@ -18,6 +18,7 @@ import {
1818} from 'lucide-react'
1919import { useParams , useRouter } from 'next/navigation'
2020import {
21+ Badge ,
2122 Breadcrumb ,
2223 Button ,
2324 Modal ,
@@ -40,12 +41,12 @@ import {
4041 TableHeader ,
4142 TableRow ,
4243} from '@/components/ui/table'
44+ import { cn } from '@/lib/core/utils/cn'
4345import type { DocumentSortField , SortOrder } from '@/lib/knowledge/documents/types'
4446import {
4547 ActionBar ,
4648 AddDocumentsModal ,
4749 BaseTagsModal ,
48- DocumentTagsCell ,
4950} from '@/app/workspace/[workspaceId]/knowledge/[id]/components'
5051import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components'
5152import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
@@ -54,7 +55,10 @@ import {
5455 useKnowledgeBaseDocuments ,
5556 useKnowledgeBasesList ,
5657} from '@/hooks/use-knowledge'
57- import { useKnowledgeBaseTagDefinitions } from '@/hooks/use-knowledge-base-tag-definitions'
58+ import {
59+ type TagDefinition ,
60+ useKnowledgeBaseTagDefinitions ,
61+ } from '@/hooks/use-knowledge-base-tag-definitions'
5862import type { DocumentData } from '@/stores/knowledge/store'
5963
6064const logger = createLogger ( 'KnowledgeBase' )
@@ -75,13 +79,13 @@ function DocumentTableRowSkeleton() {
7579 < Skeleton className = 'h-[17px] w-[120px]' />
7680 </ div >
7781 </ TableCell >
78- < TableCell className = 'px-[12px] py-[8px]' >
82+ < TableCell className = 'hidden px-[12px] py-[8px] lg:table-cell ' >
7983 < Skeleton className = 'h-[15px] w-[48px]' />
8084 </ TableCell >
81- < TableCell className = 'px-[12px] py-[8px]' >
85+ < TableCell className = 'hidden px-[12px] py-[8px] lg:table-cell ' >
8286 < Skeleton className = 'h-[15px] w-[32px]' />
8387 </ TableCell >
84- < TableCell className = 'hidden px-[12px] py-[8px] lg:table-cell ' >
88+ < TableCell className = 'px-[12px] py-[8px]' >
8589 < Skeleton className = 'h-[15px] w-[24px]' />
8690 </ TableCell >
8791 < TableCell className = 'px-[12px] py-[8px]' >
@@ -119,13 +123,13 @@ function DocumentTableSkeleton({ rowCount = 5 }: { rowCount?: number }) {
119123 < TableHead className = 'w-[180px] max-w-[180px] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]' >
120124 Name
121125 </ TableHead >
122- < TableHead className = 'w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]' >
126+ < TableHead className = 'hidden w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)] lg:table-cell ' >
123127 Size
124128 </ TableHead >
125- < TableHead className = 'w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]' >
129+ < TableHead className = 'hidden w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)] lg:table-cell ' >
126130 Tokens
127131 </ TableHead >
128- < TableHead className = 'hidden w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)] lg:table-cell ' >
132+ < TableHead className = 'w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]' >
129133 Chunks
130134 </ TableHead >
131135 < TableHead className = 'w-[11%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]' >
@@ -278,56 +282,122 @@ function formatFileSize(bytes: number): string {
278282 return `${ Number . parseFloat ( ( bytes / k ** i ) . toFixed ( 2 ) ) } ${ sizes [ i ] } `
279283}
280284
281- const getStatusDisplay = ( doc : DocumentData ) => {
282- // Consolidated status: show processing status when not completed, otherwise show enabled/disabled
285+ const AnimatedLoader = ( { className } : { className ?: string } ) => (
286+ < Loader2 className = { cn ( className , 'animate-spin' ) } />
287+ )
288+
289+ const getStatusBadge = ( doc : DocumentData ) => {
283290 switch ( doc . processingStatus ) {
284291 case 'pending' :
285- return {
286- text : 'Pending' ,
287- className :
288- 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300' ,
289- }
292+ return (
293+ < Badge variant = 'gray' size = 'sm' >
294+ Pending
295+ </ Badge >
296+ )
290297 case 'processing' :
291- return {
292- text : (
293- < >
294- < Loader2 className = 'mr-1.5 h-3 w-3 animate-spin' />
295- Processing
296- </ >
297- ) ,
298- className :
299- 'inline-flex items-center rounded-md bg-purple-100 px-2 py-1 text-xs font-medium text-[var(--brand-primary-hex)] dark:bg-purple-900/30 dark:text-[var(--brand-primary-hex)]' ,
300- }
298+ return (
299+ < Badge variant = 'purple' size = 'sm' icon = { AnimatedLoader } >
300+ Processing
301+ </ Badge >
302+ )
301303 case 'failed' :
302- return {
303- text : (
304- < >
305- Failed
306- { doc . processingError && < AlertCircle className = 'ml-1.5 h-3 w-3' /> }
307- </ >
308- ) ,
309- className :
310- 'inline-flex items-center rounded-md bg-red-100 px-2 py-1 text-xs font-medium text-red-700 dark:bg-red-900/30 dark:text-red-300' ,
311- }
304+ return doc . processingError ? (
305+ < Badge variant = 'red' size = 'sm' icon = { AlertCircle } >
306+ Failed
307+ </ Badge >
308+ ) : (
309+ < Badge variant = 'red' size = 'sm' >
310+ Failed
311+ </ Badge >
312+ )
312313 case 'completed' :
313- return doc . enabled
314- ? {
315- text : 'Enabled' ,
316- className :
317- 'inline-flex items-center rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400' ,
318- }
319- : {
320- text : 'Disabled' ,
321- className :
322- 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300' ,
323- }
314+ return doc . enabled ? (
315+ < Badge variant = 'green' size = 'sm' >
316+ Enabled
317+ </ Badge >
318+ ) : (
319+ < Badge variant = 'gray' size = 'sm' >
320+ Disabled
321+ </ Badge >
322+ )
324323 default :
325- return {
326- text : 'Unknown' ,
327- className :
328- 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300' ,
324+ return (
325+ < Badge variant = 'gray' size = 'sm' >
326+ Unknown
327+ </ Badge >
328+ )
329+ }
330+ }
331+
332+ const TAG_SLOTS = [
333+ 'tag1' ,
334+ 'tag2' ,
335+ 'tag3' ,
336+ 'tag4' ,
337+ 'tag5' ,
338+ 'tag6' ,
339+ 'tag7' ,
340+ 'number1' ,
341+ 'number2' ,
342+ 'number3' ,
343+ 'number4' ,
344+ 'number5' ,
345+ 'date1' ,
346+ 'date2' ,
347+ 'boolean1' ,
348+ 'boolean2' ,
349+ 'boolean3' ,
350+ ] as const
351+
352+ type TagSlot = ( typeof TAG_SLOTS ) [ number ]
353+
354+ interface TagValue {
355+ slot : TagSlot
356+ displayName : string
357+ value : string
358+ }
359+
360+ const TAG_FIELD_TYPES : Record < string , string > = {
361+ tag : 'text' ,
362+ number : 'number' ,
363+ date : 'date' ,
364+ boolean : 'boolean' ,
365+ }
366+
367+ /**
368+ * Computes tag values for a document
369+ */
370+ function getDocumentTags ( doc : DocumentData , definitions : TagDefinition [ ] ) : TagValue [ ] {
371+ const result : TagValue [ ] = [ ]
372+
373+ for ( const slot of TAG_SLOTS ) {
374+ const raw = doc [ slot ]
375+ if ( raw == null ) continue
376+
377+ const def = definitions . find ( ( d ) => d . tagSlot === slot )
378+ const fieldType = def ?. fieldType || TAG_FIELD_TYPES [ slot . replace ( / \d + $ / , '' ) ] || 'text'
379+
380+ let value : string
381+ if ( fieldType === 'date' ) {
382+ try {
383+ value = format ( new Date ( raw as string ) , 'MMM d, yyyy' )
384+ } catch {
385+ value = String ( raw )
329386 }
387+ } else if ( fieldType === 'boolean' ) {
388+ value = raw ? 'Yes' : 'No'
389+ } else if ( fieldType === 'number' && typeof raw === 'number' ) {
390+ value = raw . toLocaleString ( )
391+ } else {
392+ value = String ( raw )
393+ }
394+
395+ if ( value ) {
396+ result . push ( { slot, displayName : def ?. displayName || slot , value } )
397+ }
330398 }
399+
400+ return result
331401}
332402
333403export function KnowledgeBase ( {
@@ -1064,9 +1134,9 @@ export function KnowledgeBase({
10641134 </ div >
10651135 </ TableHead >
10661136 { renderSortableHeader ( 'filename' , 'Name' , 'w-[180px] max-w-[180px]' ) }
1067- { renderSortableHeader ( 'fileSize' , 'Size' , 'w-[8%]' ) }
1068- { renderSortableHeader ( 'tokenCount' , 'Tokens' , 'w-[8%]' ) }
1069- { renderSortableHeader ( 'chunkCount' , 'Chunks' , 'hidden w-[8%] lg:table-cell ' ) }
1137+ { renderSortableHeader ( 'fileSize' , 'Size' , 'hidden w-[8%] lg:table-cell ' ) }
1138+ { renderSortableHeader ( 'tokenCount' , 'Tokens' , 'hidden w-[8%] lg:table-cell ' ) }
1139+ { renderSortableHeader ( 'chunkCount' , 'Chunks' , 'w-[8%]' ) }
10701140 { renderSortableHeader ( 'uploadedAt' , 'Uploaded' , 'w-[11%]' ) }
10711141 { renderSortableHeader ( 'processingStatus' , 'Status' , 'w-[10%]' ) }
10721142 < TableHead className = 'w-[12%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]' >
@@ -1080,7 +1150,6 @@ export function KnowledgeBase({
10801150 < TableBody >
10811151 { documents . map ( ( doc ) => {
10821152 const isSelected = selectedDocuments . has ( doc . id )
1083- const statusDisplay = getStatusDisplay ( doc )
10841153
10851154 return (
10861155 < TableRow
@@ -1124,10 +1193,10 @@ export function KnowledgeBase({
11241193 </ Tooltip . Root >
11251194 </ div >
11261195 </ TableCell >
1127- < TableCell className = 'px-[12px] py-[8px] text-[12px] text-[var(--text-muted)]' >
1196+ < TableCell className = 'hidden px-[12px] py-[8px] text-[12px] text-[var(--text-muted)] lg:table-cell ' >
11281197 { formatFileSize ( doc . fileSize ) }
11291198 </ TableCell >
1130- < TableCell className = 'px-[12px] py-[8px] text-[12px]' >
1199+ < TableCell className = 'hidden px-[12px] py-[8px] text-[12px] lg:table-cell ' >
11311200 { doc . processingStatus === 'completed' ? (
11321201 doc . tokenCount > 1000 ? (
11331202 `${ ( doc . tokenCount / 1000 ) . toFixed ( 1 ) } k`
@@ -1138,7 +1207,7 @@ export function KnowledgeBase({
11381207 < span className = 'text-[var(--text-muted)]' > —</ span >
11391208 ) }
11401209 </ TableCell >
1141- < TableCell className = 'hidden px-[12px] py-[8px] text-[12px] text-[var(--text-muted)] lg:table-cell ' >
1210+ < TableCell className = 'px-[12px] py-[8px] text-[12px] text-[var(--text-muted)]' >
11421211 { doc . processingStatus === 'completed'
11431212 ? doc . chunkCount . toLocaleString ( )
11441213 : '—' }
@@ -1159,20 +1228,51 @@ export function KnowledgeBase({
11591228 { doc . processingStatus === 'failed' && doc . processingError ? (
11601229 < Tooltip . Root >
11611230 < Tooltip . Trigger asChild >
1162- < div className = { statusDisplay . className } style = { { cursor : 'help' } } >
1163- { statusDisplay . text }
1164- </ div >
1231+ < div style = { { cursor : 'help' } } > { getStatusBadge ( doc ) } </ div >
11651232 </ Tooltip . Trigger >
11661233 < Tooltip . Content side = 'top' className = 'max-w-xs' >
11671234 { doc . processingError }
11681235 </ Tooltip . Content >
11691236 </ Tooltip . Root >
11701237 ) : (
1171- < div className = { statusDisplay . className } > { statusDisplay . text } </ div >
1238+ getStatusBadge ( doc )
11721239 ) }
11731240 </ TableCell >
11741241 < TableCell className = 'px-[12px] py-[8px]' >
1175- < DocumentTagsCell document = { doc } tagDefinitions = { tagDefinitions } />
1242+ { ( ( ) => {
1243+ const tags = getDocumentTags ( doc , tagDefinitions )
1244+ if ( tags . length === 0 ) {
1245+ return < span className = 'text-[12px] text-[var(--text-muted)]' > —</ span >
1246+ }
1247+ const displayText = tags . map ( ( t ) => t . value ) . join ( ', ' )
1248+ return (
1249+ < Tooltip . Root >
1250+ < Tooltip . Trigger asChild >
1251+ < span
1252+ className = 'block max-w-full truncate text-[12px] text-[var(--text-secondary)]'
1253+ onClick = { ( e ) => e . stopPropagation ( ) }
1254+ >
1255+ { displayText }
1256+ </ span >
1257+ </ Tooltip . Trigger >
1258+ < Tooltip . Content
1259+ side = 'top'
1260+ className = 'max-h-[104px] max-w-[240px] overflow-y-auto'
1261+ >
1262+ < div className = 'flex flex-col gap-[2px]' >
1263+ { tags . map ( ( tag ) => (
1264+ < div key = { tag . slot } className = 'text-[11px]' >
1265+ < span className = 'text-[var(--text-muted)]' >
1266+ { tag . displayName } :
1267+ </ span > { ' ' }
1268+ { tag . value }
1269+ </ div >
1270+ ) ) }
1271+ </ div >
1272+ </ Tooltip . Content >
1273+ </ Tooltip . Root >
1274+ )
1275+ } ) ( ) }
11761276 </ TableCell >
11771277 < TableCell className = 'py-[8px] pr-[4px] pl-[12px]' >
11781278 < div className = 'flex items-center gap-[4px]' >
0 commit comments