mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Compare commits
1 Commits
v0.5.52
...
improvemen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6c7bd3534 |
@@ -45,6 +45,7 @@ import {
|
||||
ActionBar,
|
||||
AddDocumentsModal,
|
||||
BaseTagsModal,
|
||||
DocumentTagsCell,
|
||||
} from '@/app/workspace/[workspaceId]/knowledge/[id]/components'
|
||||
import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
@@ -53,6 +54,7 @@ import {
|
||||
useKnowledgeBaseDocuments,
|
||||
useKnowledgeBasesList,
|
||||
} from '@/hooks/use-knowledge'
|
||||
import { useKnowledgeBaseTagDefinitions } from '@/hooks/use-knowledge-base-tag-definitions'
|
||||
import type { DocumentData } from '@/stores/knowledge/store'
|
||||
|
||||
const logger = createLogger('KnowledgeBase')
|
||||
@@ -83,18 +85,17 @@ function DocumentTableRowSkeleton() {
|
||||
<Skeleton className='h-[15px] w-[24px]' />
|
||||
</TableCell>
|
||||
<TableCell className='px-[12px] py-[8px]'>
|
||||
<div className='flex flex-col justify-center'>
|
||||
<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>
|
||||
<Skeleton className='h-[15px] w-[60px]' />
|
||||
</TableCell>
|
||||
<TableCell className='px-[12px] py-[8px]'>
|
||||
<Skeleton className='h-[24px] w-[64px] rounded-md' />
|
||||
</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]'>
|
||||
<div className='flex items-center gap-[4px]'>
|
||||
<Skeleton className='h-[28px] w-[28px] rounded-[4px]' />
|
||||
@@ -127,13 +128,16 @@ function DocumentTableSkeleton({ rowCount = 5 }: { rowCount?: number }) {
|
||||
<TableHead className='hidden w-[8%] px-[12px] py-[8px] text-[12px] text-[var(--text-secondary)] lg:table-cell'>
|
||||
Chunks
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
@@ -379,6 +383,8 @@ export function KnowledgeBase({
|
||||
sortOrder,
|
||||
})
|
||||
|
||||
const { tagDefinitions } = useKnowledgeBaseTagDefinitions(id)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const knowledgeBaseName = knowledgeBase?.name || passedKnowledgeBaseName || 'Knowledge Base'
|
||||
@@ -1061,9 +1067,12 @@ export function KnowledgeBase({
|
||||
{renderSortableHeader('fileSize', 'Size', 'w-[8%]')}
|
||||
{renderSortableHeader('tokenCount', 'Tokens', 'w-[8%]')}
|
||||
{renderSortableHeader('chunkCount', 'Chunks', 'hidden w-[8%] lg:table-cell')}
|
||||
{renderSortableHeader('uploadedAt', 'Uploaded', 'w-[16%]')}
|
||||
{renderSortableHeader('processingStatus', 'Status', 'w-[12%]')}
|
||||
<TableHead className='w-[14%] py-[8px] pr-[4px] pl-[12px] text-[12px] text-[var(--text-secondary)]'>
|
||||
{renderSortableHeader('uploadedAt', 'Uploaded', 'w-[11%]')}
|
||||
{renderSortableHeader('processingStatus', 'Status', 'w-[10%]')}
|
||||
<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
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
@@ -1135,20 +1144,16 @@ export function KnowledgeBase({
|
||||
: '—'}
|
||||
</TableCell>
|
||||
<TableCell className='px-[12px] py-[8px]'>
|
||||
<div className='flex flex-col justify-center'>
|
||||
<div className='flex items-center font-medium text-[12px]'>
|
||||
<span>{format(new Date(doc.uploadedAt), 'h:mm a')}</span>
|
||||
<span className='mx-[6px] hidden text-[var(--text-muted)] xl:inline'>
|
||||
|
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className='text-[12px] text-[var(--text-muted)]'>
|
||||
{format(new Date(doc.uploadedAt), 'MMM d')}
|
||||
</span>
|
||||
<span className='hidden text-[var(--text-muted)] xl:inline'>
|
||||
{format(new Date(doc.uploadedAt), 'MMM d, yyyy')}
|
||||
</span>
|
||||
</div>
|
||||
<div className='mt-[2px] text-[12px] text-[var(--text-muted)] lg:hidden'>
|
||||
{format(new Date(doc.uploadedAt), 'MMM d')}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
{format(new Date(doc.uploadedAt), 'MMM d, yyyy h:mm a')}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</TableCell>
|
||||
<TableCell className='px-[12px] py-[8px]'>
|
||||
{doc.processingStatus === 'failed' && doc.processingError ? (
|
||||
@@ -1166,6 +1171,9 @@ export function KnowledgeBase({
|
||||
<div className={statusDisplay.className}>{statusDisplay.text}</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className='px-[12px] py-[8px]'>
|
||||
<DocumentTagsCell document={doc} tagDefinitions={tagDefinitions} />
|
||||
</TableCell>
|
||||
<TableCell className='py-[8px] pr-[4px] pl-[12px]'>
|
||||
<div className='flex items-center gap-[4px]'>
|
||||
{doc.processingStatus === 'failed' && (
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { format } from 'date-fns'
|
||||
import { Badge, Popover, PopoverAnchor, PopoverContent, Tooltip } from '@/components/emcn'
|
||||
import type { TagDefinition } from '@/hooks/use-knowledge-base-tag-definitions'
|
||||
import type { DocumentData } from '@/stores/knowledge/store'
|
||||
|
||||
/** All tag slot keys that can hold values */
|
||||
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
|
||||
fieldType: string
|
||||
}
|
||||
|
||||
interface DocumentTagsCellProps {
|
||||
document: DocumentData
|
||||
tagDefinitions: TagDefinition[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a tag value based on its field type
|
||||
*/
|
||||
function formatTagValue(value: unknown, fieldType: string): string {
|
||||
if (value === null || value === undefined) return ''
|
||||
|
||||
switch (fieldType) {
|
||||
case 'date':
|
||||
try {
|
||||
return format(new Date(value as string), 'MMM d, yyyy')
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
case 'boolean':
|
||||
return value ? 'Yes' : 'No'
|
||||
case 'number':
|
||||
return typeof value === 'number' ? value.toLocaleString() : String(value)
|
||||
default:
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field type for a tag slot
|
||||
*/
|
||||
function getFieldType(slot: TagSlot): string {
|
||||
if (slot.startsWith('tag')) return 'text'
|
||||
if (slot.startsWith('number')) return 'number'
|
||||
if (slot.startsWith('date')) return 'date'
|
||||
if (slot.startsWith('boolean')) return 'boolean'
|
||||
return 'text'
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell component that displays document tags as compact badges with overflow popover
|
||||
*/
|
||||
export function DocumentTagsCell({ document, tagDefinitions }: DocumentTagsCellProps) {
|
||||
const tags = useMemo(() => {
|
||||
const result: TagValue[] = []
|
||||
|
||||
for (const slot of TAG_SLOTS) {
|
||||
const value = document[slot]
|
||||
if (value === null || value === undefined) continue
|
||||
|
||||
const definition = tagDefinitions.find((def) => def.tagSlot === slot)
|
||||
const fieldType = definition?.fieldType || getFieldType(slot)
|
||||
const formattedValue = formatTagValue(value, fieldType)
|
||||
|
||||
if (!formattedValue) continue
|
||||
|
||||
result.push({
|
||||
slot,
|
||||
displayName: definition?.displayName || slot,
|
||||
value: formattedValue,
|
||||
fieldType,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}, [document, tagDefinitions])
|
||||
|
||||
if (tags.length === 0) {
|
||||
return <span className='text-[11px] text-[var(--text-muted)]'>—</span>
|
||||
}
|
||||
|
||||
const visibleTags = tags.slice(0, 2)
|
||||
const overflowTags = tags.slice(2)
|
||||
const hasOverflow = overflowTags.length > 0
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-[4px]' onClick={(e) => e.stopPropagation()}>
|
||||
{visibleTags.map((tag) => (
|
||||
<Tooltip.Root key={tag.slot}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Badge className='max-w-[80px] truncate px-[6px] py-[1px] text-[10px]'>
|
||||
{tag.value}
|
||||
</Badge>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
{tag.displayName}: {tag.value}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
))}
|
||||
{hasOverflow && (
|
||||
<Popover>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<PopoverAnchor asChild>
|
||||
<Badge
|
||||
variant='outline'
|
||||
className='cursor-pointer px-[6px] py-[1px] text-[10px] hover:bg-[var(--surface-6)]'
|
||||
>
|
||||
+{overflowTags.length}
|
||||
</Badge>
|
||||
</PopoverAnchor>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
{overflowTags.map((tag) => tag.displayName).join(', ')}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<PopoverContent side='bottom' align='start' maxWidth={220} minWidth={160}>
|
||||
<div className='flex flex-col gap-[2px]'>
|
||||
{tags.map((tag) => (
|
||||
<div
|
||||
key={tag.slot}
|
||||
className='flex items-center justify-between gap-[8px] rounded-[4px] px-[6px] py-[4px] text-[11px]'
|
||||
>
|
||||
<span className='text-[var(--text-muted)]'>{tag.displayName}</span>
|
||||
<span className='max-w-[100px] truncate text-[var(--text-primary)]'>
|
||||
{tag.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export { ActionBar } from './action-bar/action-bar'
|
||||
export { AddDocumentsModal } from './add-documents-modal/add-documents-modal'
|
||||
export { BaseTagsModal } from './base-tags-modal/base-tags-modal'
|
||||
export { DocumentTagsCell } from './document-tags-cell/document-tags-cell'
|
||||
|
||||
Reference in New Issue
Block a user