mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
feat(kb): added tags information to kb docs table (#2589)
* feat(kb): added tags information to kb docs table * improvement(base): table and tags styling --------- Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useParams, useRouter } from 'next/navigation'
|
import { useParams, useRouter } from 'next/navigation'
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
Button,
|
Button,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -40,6 +41,7 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table'
|
} from '@/components/ui/table'
|
||||||
|
import { cn } from '@/lib/core/utils/cn'
|
||||||
import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types'
|
import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types'
|
||||||
import {
|
import {
|
||||||
ActionBar,
|
ActionBar,
|
||||||
@@ -53,6 +55,10 @@ import {
|
|||||||
useKnowledgeBaseDocuments,
|
useKnowledgeBaseDocuments,
|
||||||
useKnowledgeBasesList,
|
useKnowledgeBasesList,
|
||||||
} from '@/hooks/use-knowledge'
|
} from '@/hooks/use-knowledge'
|
||||||
|
import {
|
||||||
|
type TagDefinition,
|
||||||
|
useKnowledgeBaseTagDefinitions,
|
||||||
|
} from '@/hooks/use-knowledge-base-tag-definitions'
|
||||||
import type { DocumentData } from '@/stores/knowledge/store'
|
import type { DocumentData } from '@/stores/knowledge/store'
|
||||||
|
|
||||||
const logger = createLogger('KnowledgeBase')
|
const logger = createLogger('KnowledgeBase')
|
||||||
@@ -73,28 +79,27 @@ function DocumentTableRowSkeleton() {
|
|||||||
<Skeleton className='h-[17px] w-[120px]' />
|
<Skeleton className='h-[17px] w-[120px]' />
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='px-[12px] py-[8px]'>
|
<TableCell className='hidden px-[12px] py-[8px] lg:table-cell'>
|
||||||
<Skeleton className='h-[15px] w-[48px]' />
|
<Skeleton className='h-[15px] w-[48px]' />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='px-[12px] py-[8px]'>
|
<TableCell className='hidden px-[12px] py-[8px] lg:table-cell'>
|
||||||
<Skeleton className='h-[15px] w-[32px]' />
|
<Skeleton className='h-[15px] w-[32px]' />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='hidden px-[12px] py-[8px] lg:table-cell'>
|
<TableCell className='px-[12px] py-[8px]'>
|
||||||
<Skeleton className='h-[15px] w-[24px]' />
|
<Skeleton className='h-[15px] w-[24px]' />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='px-[12px] py-[8px]'>
|
<TableCell className='px-[12px] py-[8px]'>
|
||||||
<div className='flex flex-col justify-center'>
|
<Skeleton className='h-[15px] w-[60px]' />
|
||||||
<div className='flex items-center font-medium text-[12px]'>
|
|
||||||
<Skeleton className='h-[15px] w-[50px]' />
|
|
||||||
<span className='mx-[6px] hidden text-[var(--text-muted)] xl:inline'>|</span>
|
|
||||||
<Skeleton className='hidden h-[15px] w-[70px] xl:inline-block' />
|
|
||||||
</div>
|
|
||||||
<Skeleton className='mt-[2px] h-[15px] w-[40px] lg:hidden' />
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='px-[12px] py-[8px]'>
|
<TableCell className='px-[12px] py-[8px]'>
|
||||||
<Skeleton className='h-[24px] w-[64px] rounded-md' />
|
<Skeleton className='h-[24px] w-[64px] rounded-md' />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className='px-[12px] py-[8px]'>
|
||||||
|
<div className='flex items-center gap-[4px]'>
|
||||||
|
<Skeleton className='h-[18px] w-[40px] rounded-full' />
|
||||||
|
<Skeleton className='h-[18px] w-[40px] rounded-full' />
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
<TableCell className='py-[8px] pr-[4px] pl-[12px]'>
|
<TableCell className='py-[8px] pr-[4px] pl-[12px]'>
|
||||||
<div className='flex items-center gap-[4px]'>
|
<div className='flex items-center gap-[4px]'>
|
||||||
<Skeleton className='h-[28px] w-[28px] rounded-[4px]' />
|
<Skeleton className='h-[28px] w-[28px] rounded-[4px]' />
|
||||||
@@ -118,22 +123,25 @@ function DocumentTableSkeleton({ rowCount = 5 }: { rowCount?: number }) {
|
|||||||
<TableHead className='w-[180px] max-w-[180px] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
<TableHead className='w-[180px] max-w-[180px] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
||||||
Name
|
Name
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className='w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
<TableHead className='hidden w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)] lg:table-cell'>
|
||||||
Size
|
Size
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className='w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
<TableHead className='hidden w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)] lg:table-cell'>
|
||||||
Tokens
|
Tokens
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className='hidden w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)] lg:table-cell'>
|
<TableHead className='w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
||||||
Chunks
|
Chunks
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className='w-[16%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
<TableHead className='w-[11%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
||||||
Uploaded
|
Uploaded
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className='w-[12%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
<TableHead className='w-[10%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
||||||
Status
|
Status
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className='w-[14%] py-[8px] pr-[4px] pl-[12px] text-[12px] text-[var(--text-secondary)]'>
|
<TableHead className='w-[12%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
||||||
|
Tags
|
||||||
|
</TableHead>
|
||||||
|
<TableHead className='w-[11%] py-[8px] pr-[4px] pl-[12px] text-[12px] text-[var(--text-secondary)]'>
|
||||||
Actions
|
Actions
|
||||||
</TableHead>
|
</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -274,58 +282,124 @@ function formatFileSize(bytes: number): string {
|
|||||||
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusDisplay = (doc: DocumentData) => {
|
const AnimatedLoader = ({ className }: { className?: string }) => (
|
||||||
// Consolidated status: show processing status when not completed, otherwise show enabled/disabled
|
<Loader2 className={cn(className, 'animate-spin')} />
|
||||||
|
)
|
||||||
|
|
||||||
|
const getStatusBadge = (doc: DocumentData) => {
|
||||||
switch (doc.processingStatus) {
|
switch (doc.processingStatus) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return {
|
return (
|
||||||
text: 'Pending',
|
<Badge variant='gray' size='sm'>
|
||||||
className:
|
Pending
|
||||||
'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',
|
</Badge>
|
||||||
}
|
)
|
||||||
case 'processing':
|
case 'processing':
|
||||||
return {
|
return (
|
||||||
text: (
|
<Badge variant='purple' size='sm' icon={AnimatedLoader}>
|
||||||
<>
|
Processing
|
||||||
<Loader2 className='mr-1.5 h-3 w-3 animate-spin' />
|
</Badge>
|
||||||
Processing
|
)
|
||||||
</>
|
|
||||||
),
|
|
||||||
className:
|
|
||||||
'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)]',
|
|
||||||
}
|
|
||||||
case 'failed':
|
case 'failed':
|
||||||
return {
|
return doc.processingError ? (
|
||||||
text: (
|
<Badge variant='red' size='sm' icon={AlertCircle}>
|
||||||
<>
|
Failed
|
||||||
Failed
|
</Badge>
|
||||||
{doc.processingError && <AlertCircle className='ml-1.5 h-3 w-3' />}
|
) : (
|
||||||
</>
|
<Badge variant='red' size='sm'>
|
||||||
),
|
Failed
|
||||||
className:
|
</Badge>
|
||||||
'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',
|
)
|
||||||
}
|
|
||||||
case 'completed':
|
case 'completed':
|
||||||
return doc.enabled
|
return doc.enabled ? (
|
||||||
? {
|
<Badge variant='green' size='sm'>
|
||||||
text: 'Enabled',
|
Enabled
|
||||||
className:
|
</Badge>
|
||||||
'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',
|
) : (
|
||||||
}
|
<Badge variant='gray' size='sm'>
|
||||||
: {
|
Disabled
|
||||||
text: 'Disabled',
|
</Badge>
|
||||||
className:
|
)
|
||||||
'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',
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return {
|
return (
|
||||||
text: 'Unknown',
|
<Badge variant='gray' size='sm'>
|
||||||
className:
|
Unknown
|
||||||
'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',
|
</Badge>
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TAG_SLOTS = [
|
||||||
|
'tag1',
|
||||||
|
'tag2',
|
||||||
|
'tag3',
|
||||||
|
'tag4',
|
||||||
|
'tag5',
|
||||||
|
'tag6',
|
||||||
|
'tag7',
|
||||||
|
'number1',
|
||||||
|
'number2',
|
||||||
|
'number3',
|
||||||
|
'number4',
|
||||||
|
'number5',
|
||||||
|
'date1',
|
||||||
|
'date2',
|
||||||
|
'boolean1',
|
||||||
|
'boolean2',
|
||||||
|
'boolean3',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
type TagSlot = (typeof TAG_SLOTS)[number]
|
||||||
|
|
||||||
|
interface TagValue {
|
||||||
|
slot: TagSlot
|
||||||
|
displayName: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAG_FIELD_TYPES: Record<string, string> = {
|
||||||
|
tag: 'text',
|
||||||
|
number: 'number',
|
||||||
|
date: 'date',
|
||||||
|
boolean: 'boolean',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes tag values for a document
|
||||||
|
*/
|
||||||
|
function getDocumentTags(doc: DocumentData, definitions: TagDefinition[]): TagValue[] {
|
||||||
|
const result: TagValue[] = []
|
||||||
|
|
||||||
|
for (const slot of TAG_SLOTS) {
|
||||||
|
const raw = doc[slot]
|
||||||
|
if (raw == null) continue
|
||||||
|
|
||||||
|
const def = definitions.find((d) => d.tagSlot === slot)
|
||||||
|
const fieldType = def?.fieldType || TAG_FIELD_TYPES[slot.replace(/\d+$/, '')] || 'text'
|
||||||
|
|
||||||
|
let value: string
|
||||||
|
if (fieldType === 'date') {
|
||||||
|
try {
|
||||||
|
value = format(new Date(raw as string), 'MMM d, yyyy')
|
||||||
|
} catch {
|
||||||
|
value = String(raw)
|
||||||
|
}
|
||||||
|
} else if (fieldType === 'boolean') {
|
||||||
|
value = raw ? 'Yes' : 'No'
|
||||||
|
} else if (fieldType === 'number' && typeof raw === 'number') {
|
||||||
|
value = raw.toLocaleString()
|
||||||
|
} else {
|
||||||
|
value = String(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
result.push({ slot, displayName: def?.displayName || slot, value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
export function KnowledgeBase({
|
export function KnowledgeBase({
|
||||||
id,
|
id,
|
||||||
knowledgeBaseName: passedKnowledgeBaseName,
|
knowledgeBaseName: passedKnowledgeBaseName,
|
||||||
@@ -379,6 +453,8 @@ export function KnowledgeBase({
|
|||||||
sortOrder,
|
sortOrder,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { tagDefinitions } = useKnowledgeBaseTagDefinitions(id)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const knowledgeBaseName = knowledgeBase?.name || passedKnowledgeBaseName || 'Knowledge Base'
|
const knowledgeBaseName = knowledgeBase?.name || passedKnowledgeBaseName || 'Knowledge Base'
|
||||||
@@ -1058,12 +1134,15 @@ export function KnowledgeBase({
|
|||||||
</div>
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{renderSortableHeader('filename', 'Name', 'w-[180px] max-w-[180px]')}
|
{renderSortableHeader('filename', 'Name', 'w-[180px] max-w-[180px]')}
|
||||||
{renderSortableHeader('fileSize', 'Size', 'w-[8%]')}
|
{renderSortableHeader('fileSize', 'Size', 'hidden w-[8%] lg:table-cell')}
|
||||||
{renderSortableHeader('tokenCount', 'Tokens', 'w-[8%]')}
|
{renderSortableHeader('tokenCount', 'Tokens', 'hidden w-[8%] lg:table-cell')}
|
||||||
{renderSortableHeader('chunkCount', 'Chunks', 'hidden w-[8%] lg:table-cell')}
|
{renderSortableHeader('chunkCount', 'Chunks', 'w-[8%]')}
|
||||||
{renderSortableHeader('uploadedAt', 'Uploaded', 'w-[16%]')}
|
{renderSortableHeader('uploadedAt', 'Uploaded', 'w-[11%]')}
|
||||||
{renderSortableHeader('processingStatus', 'Status', 'w-[12%]')}
|
{renderSortableHeader('processingStatus', 'Status', 'w-[10%]')}
|
||||||
<TableHead className='w-[14%] py-[8px] pr-[4px] pl-[12px] text-[12px] text-[var(--text-secondary)]'>
|
<TableHead className='w-[12%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)]'>
|
||||||
|
Tags
|
||||||
|
</TableHead>
|
||||||
|
<TableHead className='w-[11%] py-[8px] pr-[4px] pl-[12px] text-[12px] text-[var(--text-secondary)]'>
|
||||||
Actions
|
Actions
|
||||||
</TableHead>
|
</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -1071,7 +1150,6 @@ export function KnowledgeBase({
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{documents.map((doc) => {
|
{documents.map((doc) => {
|
||||||
const isSelected = selectedDocuments.has(doc.id)
|
const isSelected = selectedDocuments.has(doc.id)
|
||||||
const statusDisplay = getStatusDisplay(doc)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
@@ -1115,10 +1193,10 @@ export function KnowledgeBase({
|
|||||||
</Tooltip.Root>
|
</Tooltip.Root>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='px-[12px] py-[8px] text-[12px] text-[var(--text-muted)]'>
|
<TableCell className='hidden px-[12px] py-[8px] text-[12px] text-[var(--text-muted)] lg:table-cell'>
|
||||||
{formatFileSize(doc.fileSize)}
|
{formatFileSize(doc.fileSize)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='px-[12px] py-[8px] text-[12px]'>
|
<TableCell className='hidden px-[12px] py-[8px] text-[12px] lg:table-cell'>
|
||||||
{doc.processingStatus === 'completed' ? (
|
{doc.processingStatus === 'completed' ? (
|
||||||
doc.tokenCount > 1000 ? (
|
doc.tokenCount > 1000 ? (
|
||||||
`${(doc.tokenCount / 1000).toFixed(1)}k`
|
`${(doc.tokenCount / 1000).toFixed(1)}k`
|
||||||
@@ -1129,43 +1207,73 @@ export function KnowledgeBase({
|
|||||||
<span className='text-[var(--text-muted)]'>—</span>
|
<span className='text-[var(--text-muted)]'>—</span>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='hidden px-[12px] py-[8px] text-[12px] text-[var(--text-muted)] lg:table-cell'>
|
<TableCell className='px-[12px] py-[8px] text-[12px] text-[var(--text-muted)]'>
|
||||||
{doc.processingStatus === 'completed'
|
{doc.processingStatus === 'completed'
|
||||||
? doc.chunkCount.toLocaleString()
|
? doc.chunkCount.toLocaleString()
|
||||||
: '—'}
|
: '—'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='px-[12px] py-[8px]'>
|
<TableCell className='px-[12px] py-[8px]'>
|
||||||
<div className='flex flex-col justify-center'>
|
<Tooltip.Root>
|
||||||
<div className='flex items-center font-medium text-[12px]'>
|
<Tooltip.Trigger asChild>
|
||||||
<span>{format(new Date(doc.uploadedAt), 'h:mm a')}</span>
|
<span className='text-[12px] text-[var(--text-muted)]'>
|
||||||
<span className='mx-[6px] hidden text-[var(--text-muted)] xl:inline'>
|
{format(new Date(doc.uploadedAt), 'MMM d')}
|
||||||
|
|
|
||||||
</span>
|
</span>
|
||||||
<span className='hidden text-[var(--text-muted)] xl:inline'>
|
</Tooltip.Trigger>
|
||||||
{format(new Date(doc.uploadedAt), 'MMM d, yyyy')}
|
<Tooltip.Content side='top'>
|
||||||
</span>
|
{format(new Date(doc.uploadedAt), 'MMM d, yyyy h:mm a')}
|
||||||
</div>
|
</Tooltip.Content>
|
||||||
<div className='mt-[2px] text-[12px] text-[var(--text-muted)] lg:hidden'>
|
</Tooltip.Root>
|
||||||
{format(new Date(doc.uploadedAt), 'MMM d')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='px-[12px] py-[8px]'>
|
<TableCell className='px-[12px] py-[8px]'>
|
||||||
{doc.processingStatus === 'failed' && doc.processingError ? (
|
{doc.processingStatus === 'failed' && doc.processingError ? (
|
||||||
<Tooltip.Root>
|
<Tooltip.Root>
|
||||||
<Tooltip.Trigger asChild>
|
<Tooltip.Trigger asChild>
|
||||||
<div className={statusDisplay.className} style={{ cursor: 'help' }}>
|
<div style={{ cursor: 'help' }}>{getStatusBadge(doc)}</div>
|
||||||
{statusDisplay.text}
|
|
||||||
</div>
|
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Content side='top' className='max-w-xs'>
|
<Tooltip.Content side='top' className='max-w-xs'>
|
||||||
{doc.processingError}
|
{doc.processingError}
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Root>
|
</Tooltip.Root>
|
||||||
) : (
|
) : (
|
||||||
<div className={statusDisplay.className}>{statusDisplay.text}</div>
|
getStatusBadge(doc)
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className='px-[12px] py-[8px]'>
|
||||||
|
{(() => {
|
||||||
|
const tags = getDocumentTags(doc, tagDefinitions)
|
||||||
|
if (tags.length === 0) {
|
||||||
|
return <span className='text-[12px] text-[var(--text-muted)]'>—</span>
|
||||||
|
}
|
||||||
|
const displayText = tags.map((t) => t.value).join(', ')
|
||||||
|
return (
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<span
|
||||||
|
className='block max-w-full truncate text-[12px] text-[var(--text-secondary)]'
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{displayText}
|
||||||
|
</span>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content
|
||||||
|
side='top'
|
||||||
|
className='max-h-[104px] max-w-[240px] overflow-y-auto'
|
||||||
|
>
|
||||||
|
<div className='flex flex-col gap-[2px]'>
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<div key={tag.slot} className='text-[11px]'>
|
||||||
|
<span className='text-[var(--text-muted)]'>
|
||||||
|
{tag.displayName}:
|
||||||
|
</span>{' '}
|
||||||
|
{tag.value}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</TableCell>
|
||||||
<TableCell className='py-[8px] pr-[4px] pl-[12px]'>
|
<TableCell className='py-[8px] pr-[4px] pl-[12px]'>
|
||||||
<div className='flex items-center gap-[4px]'>
|
<div className='flex items-center gap-[4px]'>
|
||||||
{doc.processingStatus === 'failed' && (
|
{doc.processingStatus === 'failed' && (
|
||||||
|
|||||||
Reference in New Issue
Block a user