Skip to content

Commit 6b332ee

Browse files
committed
improvement(base): table and tags styling
1 parent 601029f commit 6b332ee

File tree

3 files changed

+162
-226
lines changed

3 files changed

+162
-226
lines changed

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

Lines changed: 162 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from 'lucide-react'
1919
import { useParams, useRouter } from 'next/navigation'
2020
import {
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'
4345
import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types'
4446
import {
4547
ActionBar,
4648
AddDocumentsModal,
4749
BaseTagsModal,
48-
DocumentTagsCell,
4950
} from '@/app/workspace/[workspaceId]/knowledge/[id]/components'
5051
import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components'
5152
import { 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'
5862
import type { DocumentData } from '@/stores/knowledge/store'
5963

6064
const 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

333403
export 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

Comments
 (0)